Every copying technique in JavaScript β spread, Object.assign, Array.slice β is shallow by default. Knowing exactly when a shallow copy is enough, and when you truly need a deep copy, prevents an entire category of "mystery mutation" bugs.
1Deep Copy vs Shallow Copy | JavaScript Tutorial - In-Depth Guide Part 1
A shallow copy duplicates an object's top-level properties, but any property holding a reference type still points at the exact same nested object.
const original = { user: { name: 'Al' } };
const shallow = { ...original };
shallow.user.name = 'Bo';
original.user.name; // 'Bo' β shared reference!Shallow Copy Recap
2Deep Copy vs Shallow Copy | JavaScript Tutorial - In-Depth Guide Part 2
A deep copy recursively duplicates every nested level, guaranteeing the copy and original share absolutely no references.
const deep = JSON.parse(JSON.stringify(original));
deep.user.name = 'Carla';
original.user.name; // still 'Bo', unaffectedTrue Deep Copy
3Deep Copy vs Shallow Copy | JavaScript Tutorial - In-Depth Guide Part 3
The JSON.parse(JSON.stringify(x)) trick is a common but flawed deep-copy technique β it silently drops functions, undefined, Dates become strings, and it throws on circular references.
JSON.parse(JSON.stringify({ fn() {}, d: new Date(), u: undefined }));
// { d: '2024-01-01T00:00:00.000Z' } β fn and u are gone!The JSON Trick's Flaws
4Deep Copy vs Shallow Copy | JavaScript Tutorial - In-Depth Guide Part 4
A hand-written recursive deep-clone function correctly handles nested objects and arrays, but must be written carefully to avoid infinite loops on circular references.
function deepClone(value, seen = new WeakMap()) {
if (typeof value !== 'object' || value === null) return value;
if (seen.has(value)) return seen.get(value);
const clone = Array.isArray(value) ? [] : {};
seen.set(value, clone);
for (const key in value) clone[key] = deepClone(value[key], seen);
return clone;
}Hand-Written Deep Clone
5Deep Copy vs Shallow Copy | JavaScript Tutorial - In-Depth Guide Part 5
Most of the time, a shallow copy is exactly what you want and is far cheaper β reach for deep copying only when you know nested data must be fully independent.
// Shallow is enough here β you control every level explicitly:
const next = { ...state, user: { ...state.user, name } };Choosing the Right Depth
6Step-by-Step Breakdown
A shallow copy duplicates an object's top-level properties, but any property holding a reference type still points at the exact same nested object.
Checkpoint: Does spreading an object ({ ...obj }) create independent copies of its nested objects?
- βYes, spread is a full deep copy
- βNo, nested objects remain shared references
A deep copy recursively duplicates every nested level, guaranteeing the copy and original share absolutely no references.
The JSON.parse(JSON.stringify(x)) trick is a common but flawed deep-copy technique β it silently drops functions, undefined, Dates become strings, and it throws on circular references.
Checkpoint: Does JSON.parse(JSON.stringify(obj)) correctly preserve a function property on the object?
- βYes, functions survive the round-trip
- βNo, functions are silently dropped
A hand-written recursive deep-clone function correctly handles nested objects and arrays, but must be written carefully to avoid infinite loops on circular references.
Most of the time, a shallow copy is exactly what you want and is far cheaper β reach for deep copying only when you know nested data must be fully independent.
Next, we'll explore 'structuredClone()'.
Level Up π
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Deep-Copy Editor State Before Presenting an Undo Preview
An accessible undo/redo feature that announces 'previous version restored' via a live region needs a genuinely independent deep-copied snapshot, so the preview shown to assistive technology cannot be silently altered by ongoing edits to the live document.
SEO Implications
- 1
No Direct SEO Effect
Copy semantics are a data-integrity concern; their SEO relevance is limited to preventing state-corruption bugs that could produce inconsistent rendered content.
Best Practices
Default to Shallow Copies, and Update Each Nested Level Explicitly
Explicitly spreading each level you intend to change (`{ ...state, user: { ...state.user, name } }`) is usually clearer and cheaper than reaching for a full deep copy of the entire state tree.
Avoid the JSON.stringify/parse Trick for Anything But Simple, Serializable Data
It silently corrupts functions, undefined, Dates, Maps, Sets, and throws on circular references β use structuredClone() or a proper library for anything beyond plain JSON-safe data.
Frequent Bugs
Believing `{ ...state }` fully isolates a copy of application state, then discovering a deeply nested mutation in one part of the app silently affected another part relying on the 'original' state.
Identify exactly which levels of the structure are mutated and copy each of those levels explicitly, or use a proper deep-copy utility if the whole tree must be independent.
Using JSON.parse(JSON.stringify(data)) to clone data containing a Date, and later code breaking because it expected a Date instance but received a string.
Use structuredClone() instead, which correctly preserves Date, Map, Set, and other structured-clonable types, or manually re-hydrate Dates after a JSON round-trip.
Real-World Examples
Cloning a Document Before an Undoable Edit
A rich-text or diagram editor needed to snapshot the entire document state before an edit so the user could undo back to it, requiring full independence between the snapshot and the live document.
function snapshotForUndo(document) {
return structuredClone(document); // full deep copy, safe for later mutation
}