Ch. 1 · JavaScript

JavaScript structuredClone and Transferables

Deep-clone Dates, Maps, Sets and cyclic graphs with structuredClone, and move ArrayBuffers between contexts instead of copying them.

~3 min readintermediateupdated Oct 4, 2026

structuredClone performs a deep clone using the structured clone algorithm. Unlike JSON.parse(JSON.stringify(value)), it preserves types such as Date, Map, Set, typed arrays and ArrayBuffer, and it can follow cyclic references. It also powers postMessage between workers and the main thread, including transferables that move ownership instead of copying.

Before you start

You should understand references versus values and why {...obj} is only a shallow copy. This article covers the clone algorithm and transfers; it does not cover serialization formats such as CBOR or custom revivers.

Step-by-step walkthrough

Step 1: Know what the algorithm clones

Expect true deep copies of plain objects and arrays, plus built-in containers (Map, Set, Date, RegExp, Blob, ArrayBuffer and typed arrays). Cyclic and shared references are reproduced, not duplicated. Functions, DOM nodes, class instances with methods, and Symbol keys cannot be cloned and throw a DataCloneError.

Step 2: Detach data with a transfer

Pass { transfer: [buffer] } to move an ArrayBuffer or MessagePort to the clone. Transferring detaches the source: its byteLength becomes 0 and any further access throws. This avoids copying large buffers and is the same mechanism postMessage uses between threads.

Step 3: Respect the boundary

The clone has no shared identity with the original: mutating one never affects the other. But a class instance loses its prototype, so an object cloned from class Session becomes a plain object. Redraw that boundary deliberately: send plain data across it, or rehydrate into the class explicitly.

Worked scenario

Run this with Node.js 17 or later (or a modern browser) to see the deep copy and the transfer.

const original = { count: 1, nested: { seen: false }, when: new Date(0) };
const copy = structuredClone(original);
copy.nested.seen = true;
console.log(original.nested.seen); // false
console.log(copy.when instanceof Date); // true

const buffer = new ArrayBuffer(8);
const moved = structuredClone(buffer, { transfer: [buffer] });
console.log(buffer.byteLength); // 0 (detached)
console.log(moved.byteLength); // 8
JavaScript

Walk through the example

The nested object is cloned recursively, so editing copy.nested leaves original.nested untouched. The Date keeps its type, which a JSON round-trip would not. In the second half, the buffer is moved: its bytes now belong to moved, and reading buffer afterwards throws because it is detached. A JSON clone would instead produce a string and lose the binary representation.

Common mistake

Assuming a clone can carry everything. Functions, WeakMap keys, promises and DOM nodes throw a DataCloneError; class methods silently become plain properties or disappear. A related mistake is using JSON.parse(JSON.stringify(value)) and expecting Date, undefined and Map to survive: JSON drops each of them.

Verify the behavior

Mutate the clone and assert the original is unchanged, including nested objects. Include a cyclic reference and confirm both copies still point to their own graph. Assert that a transferred buffer’s byteLength is 0 and that accessing it throws. Try cloning an object with a function property and confirm the expected DataCloneError.

Interview exercise

A worker needs to process a 50 MB typed array. Should the main thread clone it or transfer it, and what is the consequence?

Answer and reasoning

Transfer it. Cloning duplicates the bytes, roughly doubling peak memory and stalling both threads during the copy. Transferring moves ownership in constant time, but the sender’s buffer is detached and must not be used afterwards. If the main thread still needs the data, it should keep a copy or request a result back — the point is to choose deliberately rather than clone by default.

Follow-up discussion

Does structuredClone copy getters? It reads the property value and copies the result, so a getter runs once and the clone holds a plain value. Is it safe for untrusted data? Yes for the types it supports, but a huge or deeply nested payload can still exhaust memory, so validate size and depth before cloning input from outside your process.

Continue learning

Compare shallow and deep copying in JavaScript array copy depth and review the MDN structuredClone reference. Continue with the JavaScript interview questions.

More in JavaScript

read ✓JavaScript · mid

JavaScript Date and Timezone Pitfalls

Parse and format dates without timezone surprises: zero-based months, date-only versus date-time parsing, and why UTC storage avoids drift.

~3 min readread →
read ✓JavaScript · mid

JavaScript Number Precision and BigInt

Why 0.1 + 0.2 is not 0.3, where safe integer range ends, and when BigInt is the right tool for large identifiers and money.

~2 min readread →
esc