Deep Clone
Write deepClone(value) returning a copy that shares no references with the input.
Handle plain objects and arrays, keep Date, Map and Set intact rather than flattening them, and do not hang on a structure that contains itself.
Examples
const original = { list: [1, { deep: true }] };
const copy = deepClone(original);
copy.list[1] === original.list[1];falseConstraints
- Values may be nested arbitrarily deep
- Structures may contain cycles
Notes
- `JSON.parse(JSON.stringify(x))` is the one-liner everyone reaches for; it loses Dates, Maps, Sets and undefined, and throws on a cycle.
- `structuredClone` handles all of the above natively — and still throws on functions.
Hints
Deep Clone (reference solution)
One way to do it. The Editorial tab walks through why it is written this way.
Solution
1function deepClone(value, seen) {
2 seen = seen || new Map();
3 if (value === null || typeof value !== 'object') return value;
4 if (seen.has(value)) return seen.get(value);
5 if (value instanceof Date) return new Date(value.getTime());
6 if (value instanceof Map) {
7 const copy = new Map();
8 seen.set(value, copy);
9 value.forEach((v, k) => copy.set(k, deepClone(v, seen)));
10 return copy;
11 }
12 if (value instanceof Set) {
13 const copy = new Set();
14 seen.set(value, copy);
15 value.forEach((v) => copy.add(deepClone(v, seen)));
16 return copy;
17 }
18 const copy = Array.isArray(value) ? [] : {};
19 seen.set(value, copy);
20 Object.keys(value).forEach((k) => { copy[k] = deepClone(value[k], seen); });
21 return copy;
22}Editorial: Deep Clone
Copying all the way down
A shallow copy — spread, Object.assign, slice — copies the top level and shares everything below it. Mutating copy.nested.value changes the original, which is the bug this exists to prevent.
Approach
Recurse into objects and arrays, and remember what you have already copied.
Implementation
function deepClone(value, seen) { seen = seen || new Map(); if (value === null || typeof value !== 'object') return value; if (seen.has(value)) return seen.get(value); if (value instanceof Date) return new Date(value.getTime()); if (value instanceof Map) { const copy = new Map(); seen.set(value, copy); value.forEach((v, k) => copy.set(k, deepClone(v, seen))); return copy; } if (value instanceof Set) { const copy = new Set(); seen.set(value, copy); value.forEach((v) => copy.add(deepClone(v, seen))); return copy; } const copy = Array.isArray(value) ? [] : {}; seen.set(value, copy); Object.keys(value).forEach((k) => { copy[k] = deepClone(value[k], seen); }); return copy; }
Worth knowing
The cycle guard is the interesting part. A Map from original to copy, checked first and written before the children are filled in, is what lets a self-referencing object terminate: by the time the recursion comes back around, the copy is already registered.
JSON.parse(JSON.stringify(x)) is the one-liner everyone reaches for. It loses Date (becomes a string), Map, Set, undefined and functions, and throws outright on a cycle. structuredClone handles all of that natively and is the right answer in production — it still throws on functions, which is worth saying out loud.