Introduction
Copying objects and arrays in JavaScript can be tricky. Understanding the difference between shallow copy and deep clone is essential to avoid bugs and unexpected behavior.
The Problem
Assignment Doesn’t Copy
const original = { name: "Alice", age: 30 };
const copy = original;
copy.age = 31;
console.log(original.age); // 31 - Changed!
console.log(copy.age); // 31
// Both reference the same object!
Assignment creates a reference, not a copy.
Shallow Copy
Shallow copy creates a new object, but nested objects/arrays are still referenced.
Methods for Shallow Copy
1. Spread Operator
const original = {
name: "Alice",
address: { city: "Stockholm" },
};
const shallow = { ...original };
shallow.name = "Bob";
console.log(original.name); // 'Alice' - unchanged ✓
shallow.address.city = "Oslo";
console.log(original.address.city); // 'Oslo' - changed! ✗
2. Object.assign()
const original = { name: "Alice", age: 30 };
const shallow = Object.assign({}, original);
3. Array.slice() / Array.from()
const original = [1, 2, { value: 3 }];
const shallow = original.slice();
// or
const shallow = Array.from(original);
When Shallow Copy Works
✅ Flat objects/arrays:
const flat = { a: 1, b: 2, c: 3 };
const copy = { ...flat }; // Works perfectly
✅ Primitive values:
const data = { name: "Alice", age: 30 };
const copy = { ...data }; // All primitives copied
Deep Clone
Deep clone creates a completely independent copy, including all nested objects/arrays.
Methods for Deep Clone
1. JSON.parse(JSON.stringify())
const original = {
name: "Alice",
address: { city: "Stockholm" },
hobbies: ["reading", "coding"],
};
const deep = JSON.parse(JSON.stringify(original));
deep.address.city = "Oslo";
console.log(original.address.city); // 'Stockholm' - unchanged ✓
Limitations:
- ❌ Loses functions
- ❌ Loses
undefined - ❌ Loses Symbols
- ❌ Loses Date objects (becomes strings)
- ❌ Circular references cause errors
2. Structured Clone (Modern)
const original = {
name: "Alice",
date: new Date(),
nested: { value: 42 },
};
const deep = structuredClone(original);
Supported in:
- Node.js 17+
- Modern browsers
- Handles Dates, Maps, Sets, etc.
3. Lodash cloneDeep()
import _ from "lodash";
const deep = _.cloneDeep(original);
4. Custom Implementation
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map((item) => deepClone(item));
if (typeof obj === "object") {
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
}
Comparison
Example
const original = {
name: "Alice",
address: {
city: "Stockholm",
country: "Sweden",
},
hobbies: ["reading", "coding"],
};
// Shallow copy
const shallow = { ...original };
// Deep clone
const deep = JSON.parse(JSON.stringify(original));
Modifying Nested Properties
// Modify nested property
shallow.address.city = "Oslo";
deep.address.city = "Oslo";
console.log(original.address.city); // 'Oslo' (shallow) vs 'Stockholm' (deep)
When to Use What?
Use Shallow Copy When:
✅ Flat structures
const config = { theme: "dark", language: "en" };
const copy = { ...config };
✅ Performance matters
// Shallow copy is faster
const items = [...array];
✅ Nested objects don’t need copying
const user = { name: "Alice", metadata: sharedMetadata };
const copy = { ...user }; // metadata reference is OK
Use Deep Clone When:
✅ Nested objects need independence
const order = {
items: [{ product: "A", qty: 1 }],
customer: { name: "Alice" },
};
const copy = structuredClone(order);
✅ Modifying nested structures
const template = { settings: { theme: "dark" } };
const user1 = structuredClone(template);
const user2 = structuredClone(template);
// Each can modify settings independently
✅ Complex objects
const state = {
users: [{ id: 1, profile: { bio: "..." } }],
config: { api: { endpoint: "..." } },
};
const backup = structuredClone(state);
Performance Comparison
| Method | Speed | Limitations |
|---|---|---|
Spread (...) | Fast | Shallow only |
| Object.assign() | Fast | Shallow only |
| JSON.parse/stringify | Medium | Many limitations |
| structuredClone() | Medium | Best modern option |
| Lodash cloneDeep | Slow | Most reliable |
Common Pitfalls
1. Nested Arrays
const matrix = [
[1, 2],
[3, 4],
];
const shallow = [...matrix];
shallow[0][0] = 99;
console.log(matrix[0][0]); // 99 - changed!
Solution: Deep clone for nested arrays.
2. Circular References
const obj = { name: "Alice" };
obj.self = obj; // Circular!
// JSON.parse/stringify fails
JSON.parse(JSON.stringify(obj)); // Error!
// structuredClone handles it
structuredClone(obj); // Works!
3. Functions
const obj = {
name: "Alice",
greet: () => console.log("Hello"),
};
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.greet); // undefined - lost!
Best Practices
✅ Do This
1. Use shallow copy for flat objects
const flat = { a: 1, b: 2 };
const copy = { ...flat };
2. Use structuredClone for nested objects
const nested = { a: { b: { c: 1 } } };
const copy = structuredClone(nested);
3. Consider immutable libraries
import { produce } from "immer";
const nextState = produce(currentState, (draft) => {
draft.items.push(newItem);
});
❌ Don’t Do This
1. Don’t use shallow copy for nested objects
// ❌ Wrong for nested
const nested = { a: { b: 1 } };
const copy = { ...nested };
copy.a.b = 2; // Modifies original!
2. Don’t use JSON for objects with functions
// ❌ Loses functions
const obj = { fn: () => {} };
JSON.parse(JSON.stringify(obj)); // fn is gone!
Tools
Use our tools:
- Deep Clone Visualizer - Visualize cloning
Conclusion
Understanding copy vs clone:
Shallow copy:
- Faster
- Copies top level only
- Good for flat structures
Deep clone:
- Slower
- Copies everything recursively
- Necessary for nested structures
Choose based on:
- Data structure complexity
- Performance requirements
- Need for independence
Next Steps
- Visualize with Deep Clone Visualizer
- Learn JavaScript Objects
- Explore Performance Tips