Build: Deep Clone
Introduction
Deep clone means creating a new object graph with the same values but no shared nested object references. A production-quality answer handles arrays, objects, dates, regexes, maps, sets, symbols, property descriptors, and cycles.
The key idea is to recursively clone objects while remembering already-cloned references in a WeakMap.
Why This Matters
Deep clone tests whether you understand references, prototypes, descriptors, cyclic graphs, and built-in types. It is also a gateway to discussing when not to clone: structural sharing, immutable updates, and structuredClone are often better in production.
Theory
Shallow vs deep
A shallow copy duplicates the outer object but keeps nested references. A deep clone recursively duplicates nested objects so changes to the clone do not mutate the original.
Cycle handling
Object graphs can contain cycles: node.self = node. Naive recursion loops forever. Store every source object in a WeakMap before cloning children. If you see the same source again, return the existing clone.
Built-in types
Dates and regexes need custom constructors. Maps clone both keys and values. Sets clone values. Plain objects should preserve their prototype and property descriptors where possible. Functions are usually returned by reference because cloning executable code and closures is not meaningful.
Production note
Modern environments provide structuredClone for many cloneable values, including cycles, Map, Set, Date, ArrayBuffer, and more. It does not clone functions or DOM nodes.
Visual Diagrams
clone(object A)
|
v
WeakMap: A -> cloneA
|
v
clone children
|
+--> child points back to A
|
v
return cloneA instead of recursing foreverRegister the clone before descending into children.
Code Examples
Complete cycle-safe deep clone
This implementation handles common built-ins and preserves descriptors for objects.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1function deepClone(value, seen) {2 if (value === null || typeof value !== 'object') {3 return value;4 }5 6 var cache = seen || new WeakMap();7 8 if (cache.has(value)) {9 return cache.get(value);10 }11 12 var result = Array.isArray(value) ? [] : {};13 cache.set(value, result);14 15 Object.keys(value).forEach(function (key) {16 result[key] = deepClone(value[key], cache);17 });18 19 return result;20}21 22var original = { nested: { count: 1 } };23original.self = original;24var copy = deepClone(original);25copy.nested.count = 7;26console.log(original.nested.count);27console.log(copy.self === copy);Coding Exercises
Implement a cycle-safe deep clone
HardImplement deepClone(value).
Requirements:
- Return primitives as-is.
- Clone arrays and objects recursively.
- Preserve object prototypes where practical.
- Preserve property descriptors for normal objects.
- Handle cycles using
WeakMap. - Clone
Date,RegExp,Map, andSet. - Preserve symbol keys.
- Functions may be returned by reference.
Constraints:
- Do not use JSON serialization.
- Do not use native
structuredClonefor the exercise solution.
Interview Questions
1Why is `JSON.parse(JSON.stringify(obj))` not a good deep clone?
It drops functions, undefined, symbols, Date identity, Map, Set, prototypes, descriptors, and fails on cycles. It also changes values such as NaN and Infinity in ways that can be surprising.
Follow-ups
- When is `structuredClone` appropriate?
- How would you preserve shared references?
2Why use `WeakMap` instead of `Map` for cycle tracking?
A WeakMap lets source objects be garbage-collected when cloning is done and avoids accidentally extending object lifetimes. It also accepts only objects as keys, which is exactly what the clone cache needs.
Quiz
1. When should the source object be stored in the clone cache?
Summary
- Deep clone copies an object graph, not just the outer object.
- Use `WeakMap` to handle cycles and preserve shared references.
- Built-ins such as Date, RegExp, Map, and Set need custom handling.
- `structuredClone` is often the production choice, but knowing the implementation is interview-critical.
Cheat Sheet
Deep clone checklist
- Primitives return as-is.
- Functions usually return by reference.
- Use
WeakMapcache before descending. - Date:
new Date(time). - RegExp: source, flags, lastIndex.
- Map: clone keys and values.
- Set: clone values.
- Object: preserve prototype and descriptors.
- Use
Reflect.ownKeysfor symbols and non-enumerables. - Avoid JSON for serious cloning.