Shallow vs Deep Copy
Why copying objects in JavaScript is not always what you expect, and how to do it correctly
Overview
Key Points
- β’Primitives are copied by value; objects and arrays are copied by reference β assigning an object to a new variable does NOT create a copy
- β’Shallow copy: creates a new top-level container but nested objects/arrays still point to the same memory as the original
- β’`{...obj}`, `Object.assign({}, obj)`, and `[...arr]` / `arr.slice()` all produce shallow copies
- β’`structuredClone(obj)` is the modern standard for deep cloning β handles Date, Map, Set, RegExp, and circular structures
- β’`JSON.parse(JSON.stringify(obj))` is a quick deep clone but drops functions, undefined, Symbol keys, and converts Dates to strings
- β’Deep copy is required when you need to mutate nested state without affecting the original β common in React state updates
Examples
Reference problem β two variables pointing at the same object
const original = { name: 'Alice', scores: [95, 87, 91] };
const copy = original; // NOT a copy β same reference!
copy.name = 'Bob';
console.log(original.name); // 'Bob' β original was mutated too!
// To prove they are the same:
console.log(original === copy); // trueThis is the root cause of most unexpected mutation bugs.
Shallow copy β top level is independent but nested array is shared
const original = { name: 'Alice', scores: [95, 87, 91] };
const shallow = { ...original }; // spread = shallow copy
shallow.name = 'Bob'; // OK β top-level string is independent
console.log(original.name); // 'Alice' β
shallow.scores.push(100); // BAD β scores is still the same array!
console.log(original.scores); // [95, 87, 91, 100] β mutated!Spread only goes one level deep.
Deep copy with structuredClone β fully independent
const original = {
name: 'Alice',
scores: [95, 87, 91],
meta: { createdAt: new Date('2024-01-01') },
};
const deep = structuredClone(original);
deep.scores.push(100);
deep.meta.createdAt = new Date('2025-06-01');
console.log(original.scores); // [95, 87, 91] β untouched
console.log(original.meta.createdAt); // 2024-01-01 β untouched
console.log(deep.meta.createdAt instanceof Date); // true βJSON.parse/stringify β quick but lossy
const original = {
name: 'Alice',
greet: () => 'Hello', // function
createdAt: new Date(), // Date
score: undefined, // undefined
};
const clone = JSON.parse(JSON.stringify(original));
console.log(clone.name); // 'Alice' β
console.log(clone.greet); // undefined β function dropped
console.log(typeof clone.createdAt); // 'string' β Date β ISO string
console.log('score' in clone); // false β undefined droppedUse structuredClone instead of JSON tricks whenever possible.
Practical: immutable state update pattern (React-style)
// Simulating React state: never mutate the existing state object
const state = {
user: { name: 'Alice', age: 30 },
settings: { theme: 'dark', lang: 'en' },
};
// WRONG β mutates state directly
state.user.age = 31;
// RIGHT β shallow copy at every level you're changing
const nextState = {
...state, // copy top level
user: {
...state.user, // copy the nested object being changed
age: 31, // then override just what changed
},
};
console.log(state.user.age); // 30 β original unchanged
console.log(nextState.user.age); // 31 β
console.log(nextState.settings === state.settings); // true (shared β unchanged)For deeply nested trees, structuredClone can be simpler than chaining spreads.
Common Mistakes
- Assuming `{...obj}` creates a deep copy β it only copies one level; nested objects are still shared by reference
- Using `JSON.parse(JSON.stringify(obj))` on data that contains Date objects, functions, undefined, or Map/Set β these values are silently lost or corrupted
- Mutating an object passed as a function argument β the caller's object is modified too because JavaScript passes objects by reference
- Forgetting to copy nested arrays when doing manual spread updates β `{...state, items: state.items}` still shares the items array
Note
Default to `structuredClone()` for deep clones β it is fast, handles all built-in types correctly, and requires no libraries. Reserve `JSON.parse(JSON.stringify())` only for environments where `structuredClone` is unavailable and your data is plain JSON-safe.
Exercises
Write JavaScript code to solve each exercise. Run it and check your output.
Completed: 0 / 6 exercises
Knowledge Check
Question 1 of 4