Shallow Copy and Deep Copy in Javascript
Shallow Copy vs Deep Copy in JavaScript
Introduction
- Shallow copy: duplicates only the top level of an object or array. Any nested objects/arrays inside are still shared by reference between the original and the copy โ they aren't truly duplicated.
- Deep copy: duplicates everything, including nested structures, so the original and the copy share no references at all.
By default, assigning one JavaScript object to another variable just copies a reference (a pointer to the same spot in memory), not the actual data.
Illustrating the problem:
let person = { name: "Alice" };
let alias = person;
alias.name = "Bob";
console.log(person); // { name: "Bob" }
console.log(alias); // { name: "Bob" }
Both variables point at the same object, so changing one changes the other. This is "copy by reference," and it's usually not what you want when you intend to create an independent copy.
Shallow Copy
Option 1: Object.assign()
let original = { name: "Alice" };
let copy = Object.assign({}, original);
copy.name = "Carol";
console.log(original); // unaffected
console.log(copy); // updated
Option 2: Spread syntax
let original = { name: "Alice" };
let copy = { ...original, role: "Engineer" };
copy.name = "Carol";
console.log(original);
console.log(copy);
Limitation of shallow copy
It only duplicates the first level of properties. Any nested object inside stays shared.
const person = {
name: "Dave",
city: "Delhi",
education: {
school: "Central High",
college: "IIT",
},
};
let clone = Object.assign({}, person);
clone.city = "Mumbai"; // works independently
clone.education.college = "NIT"; // also changes person.education.college!
console.log(person);
console.log(clone);
The top-level city field copies fine, but education.college is shared, so editing it on the clone also changes the original.
Deep Copy
Method 1: JSON round-trip
Convert the object to a JSON string, then parse it back โ this produces a brand-new structure with no shared references.
const person = {
name: "Dave",
city: "Delhi",
education: {
school: "Central High",
college: "IIT",
},
};
let clone = JSON.parse(JSON.stringify(person));
clone.city = "Mumbai";
clone.education.college = "NIT";
console.log(person); // untouched
console.log(clone); // fully independent
Limitation: the JSON round-trip silently drops anything JSON can't represent โ functions and Date/time values included in the object won't survive the conversion.
const person = {
name: "Dave",
city: "Delhi",
education: { school: "Central High", college: "IIT" },
greet: function () {
console.log("Hi, I'm", this.name);
},
};
let clone = JSON.parse(JSON.stringify(person));
// clone.greet is now undefined โ the function was lost
Method 2: Lodash's cloneDeep
Lodash is a popular JS utility library with many helper functions, including a robust deep-clone that preserves things JSON can't, like functions.
// include lodash via CDN or npm, then:
let clone = _.cloneDeep(person);
console.log(person === clone); // false โ separate objects
clone.name = "Eve";
clone.education.college = "NIT";
console.log(person); // untouched
console.log(clone); // independent, and greet() still works
Alternative: Prototype-based sharing (__proto__)
Rather than copying, you can link one object's prototype to another so it inherits shared behavior/properties while still being able to override them locally.
const base = {
name: "Himanshu",
city: "Ranchi",
describe() {
console.log(`${this.name} from ${this.city}`);
},
};
const other = {
name: "Shekhar",
describe() {
console.log(`${this.name} from ${this.city}`);
},
};
other.__proto__ = base;
other.city = "Bengaluru"; // own property, doesn't affect base
console.log(base.city); // "Ranchi"
console.log(other.city); // "Bengaluru"