ESC

Type to search the knowledge base.

Structured Clone

Deep-clone supported types with structuredClone — what transfers, what throws, vs JSON tricks and MessageChannel history.

intermediate3 min read
  • javascript
  • structured-clone

You need a deep copy that handles Date, Map, Set, circular refs, and binary data. JSON.parse(JSON.stringify(x)) drops types and dies on cycles. structuredClone is the platform algorithm browsers already used for postMessage — now a global function.

const original = {
  d: new Date(),
  m: new Map([['a', 1]]),
  s: new Set([1, 2]),
  n: 1n,
  buf: new Uint8Array([1, 2, 3]),
};
original.self = original; // cycle

const copy = structuredClone(original);
copy.m.get('a'); // 1
copy.self === copy; // true
copy.self !== original; // true
copy.d instanceof Date; // true

What clones / what fails

Supported (common) Throws DataCloneError
plain objects/arrays functions
Date, RegExp DOM nodes
Map, Set symbols as values (as property values limited)
ArrayBuffer, typed arrays class instances lose methods (become plain data if cloneable fields only — prototypes not preserved as class)
booleans, numbers, strings, bigint getters that throw, some platform objects
// DataCloneError
// structuredClone({ fn: () => {} });
// structuredClone(document.body);

Cloned objects are data, not live class instances with methods on the prototype you defined — reconstruct domain objects after clone if needed.

Transferables

const buffer = new ArrayBuffer(1024);
const copy = structuredClone({ buffer }, { transfer: [buffer] });
// buffer is detached in the original side — zero-copy move
buffer.byteLength; // 0

Same transfer list idea as postMessage. Use when shipping large binaries to workers without duplicating memory.

vs alternatives

Approach Pros Cons
structuredClone real types, cycles no functions; availability old browsers
JSON round-trip ubiquitous loses Date/Map/undefined/cycles
Lodash cloneDeep functions optional modes bundle size; still can’t clone DOM
manual full control easy to get wrong
// polyfill path for older targets: history hack
// const clone = (v) => {
//   const mc = new MessageChannel();
//   ... async only — structuredClone is sync
// };

Prefer native structuredClone with a small polyfill package only if browserslist demands it.

React state note

setState(structuredClone(state)); // deep copy — heavy
// Usually better: immutable updates at the changed path

Deep cloning entire app state every action is a performance footgun. Use structuredClone for isolated values (worker messages, undo snapshots of a document model).

Interview answer (out loud)

“structuredClone deep-clones data using the HTML structured clone algorithm — Dates, Maps, Sets, cycles, binaries. Functions and DOM nodes throw. You can transfer ArrayBuffers for zero-copy. It’s better than JSON for real data; I still do path-based immutable updates for React state instead of cloning everything.”

Workers and round-trips

// main
worker.postMessage(structuredClone(state)); // often redundant — postMessage clones already
worker.postMessage(state); // clone built-in

Don’t double-clone. Use structuredClone when you need a same-realm deep copy without messaging.

Prototype loss example

class Point {
  constructor(x, y) { this.x = x; this.y = y; }
  dist() { return Math.hypot(this.x, this.y); }
}
const p = structuredClone(new Point(3, 4));
// p is a plain object {x,y} — no dist method

Re-hydrate: Object.assign(new Point(0,0), p) or a custom fromJSON.

Further reading

Related guides