Structured Clone & Deep Copy
MedCopying objects in JavaScript is deceptively tricky. The spread operator and Object.assign only create shallow copies. JSON.parse(JSON.stringify()) works for simple data but breaks on Date, Map, Set, RegExp, undefined, and circular references. ES2022 introduced structuredClone() which handles all of these correctly.
Interactive Visualization
Object Patterns
Old Way
const user = { first: "Alice", last: "Smith" };
const full = user.first + " " + user.last;
// Must compute manually every timeModern Way
Click transform to see
Key Points
- Spread (...) and Object.assign create shallow copies — only top-level properties are duplicated
- Nested objects in shallow copies still share references with the original
- JSON.parse(JSON.stringify()) loses undefined, functions, Date objects, Map, Set, and RegExp
- structuredClone() (ES2022) handles nested objects, Date, Map, Set, RegExp, and circular references
- structuredClone() cannot clone functions, DOM nodes, Symbols, or property descriptors
- For custom deep clone logic, recursion with type checking handles edge cases JSON cannot
Code Examples
Shallow Copy Pitfall
const original = { name: 'Alice', scores: [90, 85, 92], address: { city: 'NYC' }, } const shallow = { ...original } shallow.name = 'Bob' shallow.scores.push(100) shallow.address.city = 'LA' console.log(original.name) // 'Alice' (copied) console.log(original.scores) // [90, 85, 92, 100] (shared!) console.log(original.address.city) // 'LA' (shared!)
Spread only copies top-level properties. Nested arrays and objects are still shared references.
JSON Round-Trip Limitations
const data = { date: new Date('2024-01-01'), items: new Set([1, 2, 3]), pattern: /hello/gi, callback: () => 'hi', value: undefined, } const clone = JSON.parse(JSON.stringify(data)) console.log(typeof clone.date) // 'string' (not Date!) console.log(clone.items) // {} (not Set!) console.log(clone.callback) // undefined (dropped!)
JSON serialization converts Date to string, destroys Set/Map/RegExp, silently drops functions and undefined.
structuredClone Handles Complex Types
const original = { date: new Date('2024-01-01'), items: new Set([1, 2, 3]), nested: new Map([['key', { deep: true }]]), } const clone = structuredClone(original) clone.items.add(4) clone.nested.get('key').deep = false console.log(original.items.size) // 3 (unchanged) console.log(original.nested.get('key').deep) // true (unchanged) console.log(clone.date instanceof Date) // true
structuredClone creates a true deep copy preserving Date, Set, Map, RegExp, and circular references.
Recursive Deep Clone
function deepClone(value) { if (value === null || typeof value !== 'object') return value if (value instanceof Date) return new Date(value) if (value instanceof RegExp) return new RegExp(value.source, value.flags) if (value instanceof Map) { return new Map(Array.from(value, ([k, v]) => [deepClone(k), deepClone(v)])) } if (value instanceof Set) { return new Set(Array.from(value, deepClone)) } const clone = Array.isArray(value) ? [] : {} for (const key of Object.keys(value)) { clone[key] = deepClone(value[key]) } return clone }
A manual recursive approach handles each type individually. structuredClone is preferred when available.
Common Mistakes
- Assuming spread or Object.assign creates a deep copy
- Using JSON round-trip on data containing Date, Map, Set, or undefined
- Forgetting that structuredClone throws on functions, DOM nodes, and Symbols
- Not handling circular references when writing custom deep clone logic
Interview Tips
- Explain shallow vs deep copy with a concrete nested object example
- Know limitations of each approach: spread (shallow), JSON (loses types), structuredClone (no functions)
- structuredClone handles circular references — a common follow-up question
- Be ready to implement a recursive deepClone