structuredClone()

The deep copy the language lacked for twenty years. It replaces the JSON round trip and is better in almost every way — but it drops class prototypes and refuses anything it cannot serialise.

Global functionES2021 era (HTML standard)Live demo
Common call
const copy = structuredClone(value)
Returns
an independent deep copy
Replaces
JSON.parse(JSON.stringify(value))
Watch out
throws on functions; class instances become plain objects
structuredClone(valuevalue — Anything supported by the structured clone algorithm. Functions, symbols, DOM nodes and Error subclass details are not, and throw DataCloneError.type: any · required)
→ any

Demo

Live evaluation
Try:
Inputs
jsonstringJSON with a nested "n" object
Output
(o => { const c = structuredClone(o); c.n.x = 99; return [o.n.x, c.n.x]; })(JSON.parse('{"n":{"x":1}}'))
[1, 99]

The pair is [original.n.x, copy.n.x] after writing 99 into the COPY nested object. The original still reads 1, which is what makes this a genuine deep copy — do the same with Object.assign or a spread and both would read 99, because those copy the nested reference rather than the nested object. What the demo cannot show through JSON input is everything else structuredClone handles that JSON cannot: cycles, Maps, Sets, Dates and typed arrays.

Parameters

NameTypeRequiredDescription
valueanyyesAnything supported by the structured clone algorithm. Functions, symbols, DOM nodes and Error subclass details are not, and throw DataCloneError.

Return value

any — A deep copy. Handles cycles, Map, Set, Date, RegExp, ArrayBuffer and typed arrays. Throws DataCloneError for functions, symbols and DOM nodes.

Common patterns

Deep copy before mutating
The everyday use.
const draft = structuredClone(state);
draft.items.push(item);
Copy a Map or Set
JSON loses these entirely.
const copy = structuredClone(new Map([["a", 1]]));
Keep the prototype
structuredClone does not.
Object.assign(Object.create(Object.getPrototypeOf(o)), structuredClone({...o}));

Examples

1. Genuinely deep
const o = {a: {b: 1}}; const c = structuredClone(o); c.a.b = 9; o.a.b
Returns
1
2. Clones a Map
structuredClone(new Map([["a", 1]]))
Returns
Map(1) {'a' => 1}
3. Clones a Date
structuredClone(new Date(0)).toISOString()
Returns
'1970-01-01T00:00:00.000Z'
4. Handles cycles
const o = {}; o.self = o; structuredClone(o).self === structuredClone(o)
Returns
false, but the copy self-reference is intact
5. Functions throw
structuredClone({fn: () => 1})
Returns
DataCloneError: () => 1 could not be cloned
6. Prototype is lost
class C {} structuredClone(new C()) instanceof C
Returns
false

Pitfalls

1. Functions and symbols throw
DataCloneError, not a silent omission — which is a real difference from the JSON round trip, where a function property simply disappears. An object carrying a callback cannot be cloned at all, so state holding handlers needs them stripped first.
Throws
structuredClone({onDone: () => {}})
DataCloneError
Strip them
const {onDone, ...data} = obj;
structuredClone(data)
clones
2. Class instances become plain objects
The prototype is not part of the clone, so methods and instanceof are gone while the data survives. A cloned Date is still a Date — the algorithm special-cases the built-ins — but your own classes are not special-cased.
No longer an instance
class C { hi() {} }
structuredClone(new C()).hi
undefined
Reconstruct
Object.assign(new C(), structuredClone({...instance}))
methods back
3. Getters are flattened to values
A computed property is invoked once and its result stored, so the copy has a static value where the original had live behaviour. The same thing Object.assign does, and worth knowing before cloning a config object full of getters.
Frozen value
structuredClone({get n() { return Date.now(); }})
{n: a fixed number}
Copy descriptors
Object.defineProperties({}, Object.getOwnPropertyDescriptors(o))
still a getter
4. It is not free
A deep copy walks the whole structure, so cloning large state on every update is a real cost. It is still generally faster than the JSON round trip, but the right fix for hot paths is usually to copy only the branch you are changing.
Clones everything
structuredClone(hugeState)
O(size of state)
Copy one branch
({...state, items: [...state.items, item]})
shallow where it can be

When to use

Use it
  • Deep-copying plain data before mutating it
  • Data containing Maps, Sets, Dates, RegExps or typed arrays
  • Structures with cycles, which JSON cannot handle
  • Anywhere JSON.parse(JSON.stringify(x)) currently appears
Reach for something else
  • The data contains functions → strip them, or copy manually
  • You need the prototype preserved → reconstruct after cloning
  • A shallow copy is enough → spread, which is far cheaper
  • Very large state on a hot path → copy only what changes

Notes

Complexity
O(n) in the total size of the structure
Return
A new, fully independent value
CPython impl
Not V8 — the structured clone algorithm is defined by the HTML standard
Memory
Allocates a full copy
Thread-safe
Single-threaded; the same algorithm underlies postMessage between workers

FAQ

structuredClone, in almost every case. The JSON round trip silently drops undefined, functions and symbols, turns Dates into strings, loses Maps and Sets entirely, and throws on cycles. structuredClone handles all of those correctly or fails loudly.

structuredClone(new Map([["a", 1]]));              // a Map
JSON.parse(JSON.stringify(new Map([["a", 1]])));   // {}

History

HTML5
The structured clone algorithm defined for postMessage, but not exposed directly.
2021
structuredClone exposed as a global in browsers and Node 17, finally giving JavaScript a built-in deep copy.