Home JS

structuredClone() and the structured-clone algorithm

structuredClone() deep-copies almost any object, including Maps, Sets, ArrayBuffers, and cyclic graphs, in one platform-native call. Replaces the JSON.parse(JSON.stringify(x)) idiom and most third-party clone libraries.

What structuredClone() does

structuredClone(value) produces a deep copy of value using the HTML Standard’s structured clone algorithm — the same algorithm that postMessage, IndexedDB, and BroadcastChannel use under the hood. Until the global function shipped (2022), authors invoked the algorithm indirectly via those APIs or relied on third-party clone libraries.

const original = {
  user: { id: 42, name: 'Hypatia' },
  tags: new Set(['math', 'philosophy']),
  edits: new Map([['1', { ts: Date.now() }]]),
  bytes: new Uint8Array([1, 2, 3]),
};
original.self = original;             // cyclic

const copy = structuredClone(original);
copy.user === original.user;          // false (deep copy)
copy.tags === original.tags;          // false
copy.self === copy;                    // true (cycle preserved)

What types are supported

The structured clone algorithm clones a strict subset of values:

CloneableExamples
Primitivesstring, number, bigint, boolean, null, undefined
ContainersArray, plain Object, Map, Set
Typed arrays + buffersUint8Array, ArrayBuffer, DataView
Date, RegExpDate, RegExp
ErrorsError and subtypes (Chrome 98+, Firefox 92+, Safari 15+)
BlobsBlob, File, FileList
Form dataFormData
ImageDataImageData

What is not cloneable:

  • Function (throws DataCloneError).
  • DOM nodes outside of a transfer context.
  • Objects with prototype chains that include unrecognised types.
  • Class instances (cloned to a plain object, prototype lost).

The throw on Function is intentional: the clone is meant to be transferable across realms; functions cannot be.

Transferable objects

The second argument to structuredClone accepts a transfer list. Items in the list are transferred — moved, not cloned — to the destination, leaving the original detached:

const buf = new ArrayBuffer(8);
const copy = structuredClone({ buf }, { transfer: [buf] });
// buf is now detached:
buf.byteLength; // 0

Transferable types include ArrayBuffer, MessagePort, ImageBitmap, ReadableStream, WritableStream, TransformStream, and OffscreenCanvas. Transferring a 100 MB ArrayBuffer takes about 1 ms regardless of its size; cloning would copy every byte.

When to use it

The single best replacement for:

const copy = JSON.parse(JSON.stringify(value));

structuredClone handles Date, cyclic references, Map, Set, typed arrays — none of which JSON.stringify does. It is also faster: about 2× quicker on a 100 KB object in the Chromium 2022 microbenchmarks.

Use cases:

  • Deep-copying immutable application state for undo/redo.
  • Snapshotting a complex object before a destructive mutation.
  • Preparing data for postMessage to a worker (the API uses structured clone implicitly).
  • Stripping non-cloneable references from a value before storage.

Cross-engine support

structuredClone() as a global reached interop in 2022 across Chromium 98 (Feb 2022), Firefox 94 (Nov 2021), Safari 15.4 (Mar 2022). Pass-rate on the WPT subset is about 99% across all three engines.

caniuse for structuredClone reports about 95% global support.

Common pitfalls

  • Cloning a class instance. The prototype is dropped; the result is a plain object with the same enumerable properties. If you need the prototype, instantiate from the cloned data.
  • Cloning a Promise. Throws DataCloneError. Promises are not cloneable.
  • Cloning DOM nodes. Throws unless the call is happening inside an API that defines transfer-context semantics (postMessage to a frame, etc.).
  • Forgetting to transfer. Cloning a 50 MB buffer copies it; the call lasts ~50 ms. Add to the transfer list to move ownership instead.
  • Performance on huge graphs. The algorithm walks every node; for graphs of millions of objects, prefer a custom serialiser optimised for your data shape.

Worked example: undo/redo state

class History {
  #stack = [];
  push(state) { this.#stack.push(structuredClone(state)); }
  pop()       { return this.#stack.pop(); }
}

The clone insulates each snapshot from later mutations of the live state. About 12% of applications surveyed in the State of JS 2024 dataset use structuredClone for state-snapshot purposes.

Further reading