What is a Copy?
When we copy an object or array in JavaScript, we aim to create a new structure that doesn't affect the original one when modified.
Shallow Copy
A shallow copy only duplicates the top-level properties. If those properties are objects or arrays, the copy will share references to those nested values β not duplicate them.
Example
const original = {
name: "Alice",
address: { city: "NY" }
};
const shallowCopy = { ...original };
shallowCopy.address.city = "LA";
console.log(original.address.city); // π₯ "LA" β original also changed!
Only the outer object was copied. The inner address object is still shared.
What about primitive values?
in this example:
const original = {
name: "Alice",
address: { city: "NY" }
};
const shallowCopy = { ...original };
shallowCopy.address.city = "LA";
console.log(original.address.city); // π₯ "LA" β original also changed!
if i do this:
shallowCopy.name = "Alina"
Will it change the original too?
No, original.name will stay "Alice".
Why?
Because name is a primitive value (a string), and when you use the spread operator (...), it creates a new copy of that value. Primitives (strings, numbers, booleans, etc.) are copied by value.
console.log(original.name); // "Alice"
console.log(shallowCopy.name); // "Alina"
| Property | Type | Copied How? | Changing It Affects Original? |
|---|---|---|---|
name | Primitive | By value | β No |
address.city | Object | By reference | β Yes |
Deep Copy
A deep copy duplicates all levels of the object β including nested ones β creating a fully independent clone.
Example Using JSON
const deepCopy = JSON.parse(JSON.stringify(original));
This works well for objects with only primitive types, but has limitations:
- β Functions are lost
- β
Date,undefined,Map,Set,RegExptypes are not preserved - β Cannot handle circular references
Example Using Lodash
import cloneDeep from 'lodash/cloneDeep';
const deepCopy = cloneDeep(original);
Your Key Questions Answered
Why can we use const and still change object contents?
const prevents reassignment of the variable, but it doesn't make the object immutable.
const obj = { a: 1 };
obj.a = 2; // β
Allowed
obj = { a: 3 }; // β Not allowed
//The reference stays the same, but the contents can change.
So⦠in a shallow copy:
- Primitive properties (like numbers and strings) are copied by value β
- Nested objects or arrays are copied by reference β
- Changing a nested value changes it in both the original and the copy
React Use Case: Why This Matters
Scenario:
Youβre updating part of the state that contains nested structures (e.g., an array of tasks with tags).
const [tasks, setTasks] = useState([
{ id: 1, title: "Task 1", tags: ["urgent"] }
]);
// Incorrect (shallow copy of array, mutates nested array)
const newTasks = [...tasks];
newTasks[0].tags.push("work");
setTasks(newTasks);
This mutates the nested tags array β which React might not detect properly.
Correct (deep-ish copy of affected task)
const newTasks = tasks.map((task, i) =>
i === 0 ? { ...task, tags: [...task.tags, "work"] } : task
);
setTasks(newTasks);
This copies each level to avoid mutation.
Summary
| Concept | Description |
|---|---|
| Shallow Copy | Copies only the first level of properties. Nested objects/arrays are still referenced. |
| Deep Copy | Recursively copies everything, including nested objects and arrays. |
Spread (...) | Makes a shallow copy. |
Object.assign() | Shallow copy as well. |
JSON.parse(JSON.stringify()) | Deep-ish copy, but not suitable for all types. |
lodash.cloneDeep() | Full-featured deep clone with support for all types. |
When to Use Which?
- Use shallow copy when working with flat objects or when you donβt need to touch nested structures.
- Use deep copy when:
- You're updating nested values
- You want full separation between the original and the copy
- You're using React state and need immutability
