function deepClone(value) {
if (typeof structuredClone === "function") {
return structuredClone(value);
}
// Fallback for older runtimes (loses Dates, Maps, functions).
return JSON.parse(JSON.stringify(value));
}
const original = { user: { name: "Ada", roles: ["admin"] } };
const copy = deepClone(original);
copy.user.roles.push("editor");
console.log(original.user.roles); // ["admin"] — untouched
console.log(copy.user.roles); // ["admin", "editor"][ 'admin' ]
[ 'admin', 'editor' ]Open this snippet in the editor
Launches a fresh code space with this JavaScript already loaded — edit it, share the link, or keep building.
How it works
A spread (`{...obj}`) or `Object.assign` only copies the top level, so nested objects and arrays stay shared between the original and the copy. Mutating `copy.user.roles` would then also change `original.user.roles` — a frequent and genuinely hard-to-spot source of bugs, especially in state management where "why did the previous state change?" can cost an afternoon.
`structuredClone`, available in modern browsers and Node 17+, walks the entire object graph and produces a genuinely independent value. It's the right default in 2026 because it also handles things JSON can't: `Date`, `Map`, `Set`, `ArrayBuffer`, typed arrays, and even circular references. One call, correct for the overwhelming majority of plain-data cases.
The `JSON.parse(JSON.stringify(...))` fallback is the old standby and works for simple data, but it silently drops or mangles anything JSON can't represent. `Date` objects come back as strings, `undefined` and functions vanish, `Map` and `Set` turn into empty objects, and a circular reference throws outright. Keep it only as a fallback for ancient runtimes, and know its blind spots.
Neither built-in approach preserves class identity. A cloned instance of your own class becomes a plain object — its prototype, methods, and any private fields are gone. When you need real instances back, write a custom clone (below) or give the class its own `clone()` method that reconstructs itself.
Variations
structuredClone with circular references
Where the JSON trick throws, structuredClone just works — including objects that reference themselves.
const node = { name: "root" };
node.self = node; // circular
const copy = structuredClone(node);
console.log(copy.self === copy); // true — cycle preserved
console.log(copy === node); // false — independent copyCustom recursive clone
When you need full control (or to preserve class instances), a small recursive clone handles arrays and plain objects and you extend it for the types you care about.
function clone(value) {
if (value === null || typeof value !== "object") return value;
if (Array.isArray(value)) return value.map(clone);
if (value instanceof Date) return new Date(value);
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, clone(v)]),
);
}Snapshotting state before a mutation
Immutable update patterns (Redux, undo/redo stacks, optimistic UI) rely on never mutating the previous state. Deep-clone the slice you're about to change so the old reference stays a faithful snapshot you can roll back to.
function applyEdit(state, edit) {
const next = structuredClone(state);
next.document.title = edit.title; // mutate the copy freely
return next; // old state is untouched
}Common mistakes & good to know
- structuredClone throws on values it can't clone — functions, DOM nodes, class instances with private fields. Clone plain data, not live objects.
- Neither structuredClone nor the JSON trick restores a prototype chain. A cloned class instance becomes a plain object; write a custom clone if you need real instances.
- Deep cloning large structures isn't free. If you only change one branch, copy along that path (spread the affected levels) instead of cloning the whole tree.
- The JSON fallback turns Dates into strings and drops undefined/functions/Maps. Don't use it when the data contains those.
Frequently asked questions
Why isn't the spread operator enough?
Spread and Object.assign are shallow — they copy the top level but keep references to nested objects and arrays. Mutating a nested value on the copy also mutates the original.
Is structuredClone widely supported?
Yes — all current browsers and Node 17+. It's the recommended default. Keep a JSON fallback only if you must support very old runtimes.
Does it clone functions or class methods?
No. structuredClone throws on functions and doesn't preserve class prototypes. For those, write a custom clone or a clone() method on the class.
Is deep cloning a performance concern?
For small objects, no. For large or deeply nested structures cloned on a hot path, yes — prefer structural sharing, copying only the branch you change.
Related snippets
Previous
Debounce a function in JavaScript
Next
Group a list of dictionaries by key in Python