structuredClone() Reference
WeakMap errors, prototype loss, transferables, and Node/browser support with live examples. 100% client-side.
| Feature | structuredClone() | JSON round-trip |
|---|---|---|
| Date | ✅ Date instance | ❌ string |
| Map / Set | ✅ Map / Set | ❌ {} |
| RegExp | ✅ RegExp instance | ❌ {} |
| NaN / Infinity | ✅ preserved | ❌ null |
| undefined | ✅ preserved | ❌ stripped |
| BigInt | ✅ preserved | ❌ throws |
| ArrayBuffer | ✅ cloned | ❌ throws |
| Circular refs | ✅ supported | ❌ throws |
| Functions | ❌ throws | ❌ silently stripped |
structuredClone()Core APIstructuredClone(value, options?)Creates a deep clone of a value using the structured clone algorithm. The clone is a completely independent copy — modifications to the clone do not affect the original and vice versa. Handles nested objects, circular references, and many built-in types that JSON cannot handle.
transfer optionCore APIstructuredClone(value, { transfer: [transferable, ...] })The transfer option accepts an array of Transferable objects (e.g. ArrayBuffer, MessagePort, ReadableStream) that are moved rather than cloned into the result. After transfer, the original Transferable is detached and unusable — ownership is transferred to the clone.
Circular ReferencesCore APIstructuredClone(valueWithCircularRef)Unlike JSON.stringify(), structuredClone() correctly handles circular references — objects that contain references back to themselves or to ancestor objects in the same graph. The algorithm tracks visited objects and preserves the circular structure in the clone.
vs JSON.parse / JSON.stringifyCore APIstructuredClone(value) vs JSON.parse(JSON.stringify(value))The JSON round-trip (JSON.parse(JSON.stringify(obj))) is a common but flawed cloning idiom. It silently corrupts or discards Dates (converted to strings), undefined (stripped from objects), functions (stripped), Map/Set (converted to {}), RegExp (converted to {}), NaN/Infinity (converted to null), and throws on circular references.
DataCloneErrorCore API// Throws: DOMException { name: 'DataCloneError' }structuredClone() throws a DataCloneError (a DOMException with name 'DataCloneError') when it encounters an uncloneable value anywhere in the object graph: a Function, a Symbol value, a DOM node, a WeakMap/WeakSet, or other platform-specific objects.
You deep-copy an object with JSON.parse(JSON.stringify(obj)) — it works until the object contains a Date (becomes a string), a Map (becomes {}), a RegExp (becomes {}), undefined (deleted), or Infinity (becomes null). The JSON roundtrip is a lossy deep clone. structuredClone() handles Date, Map, Set, ArrayBuffer, circular references, and built-in Error objects correctly, but it still throws for functions, DOM nodes, WeakMap, and WeakSet, and it does not preserve custom prototype chains.
Quick Answers
structuredClone(new WeakMap())andstructuredClone(new WeakSet())throwDataCloneError.structuredClone()does not preserve custom prototypes. Class instances become plain objects.- Built-in
Errorobjects are supported, but functions, DOM nodes, and symbol values are not. structuredClone()is available in Node.js 17+ and modern browsers.- Use
structuredClone(value, { transfer: [arrayBuffer] })to move large buffers without copying them.
Why This Reference (Not MDN)
MDN’s structuredClone() page covers the basics but it does not answer the most common implementation questions in one place: why WeakMap throws DataCloneError, whether the prototype chain survives, which Node.js versions support the API, and when the transfer option matters. This reference puts those answers first, then covers supported types, unsupported types, and the JSON round-trip comparison.
What Is structuredClone()?
structuredClone() is a native browser and Node.js global function that creates a deep clone of a value using the structured clone algorithm — the same algorithm used by the Web APIs for passing data between Workers, storing data in IndexedDB, and sending messages via postMessage().
const original = { user: { name: "Alice", scores: [1, 2, 3] } };
const clone = structuredClone(original);
clone.user.name = "Bob";
clone.user.scores.push(4);
console.log(original.user.name); // Alice — unchanged
console.log(original.user.scores); // [1, 2, 3] — unchanged
Unlike the common JSON.parse(JSON.stringify(obj)) pattern, structuredClone() correctly handles Dates, RegExp, Map, Set, ArrayBuffer, circular references, NaN, Infinity, BigInt, and more.
Availability: Node.js 17+, Chrome 98+, Firefox 94+, Safari 15.4+, Deno 1.14+.
Syntax
structuredClone(value)
structuredClone(value, { transfer: [transferable, ...] })
Parameters
| Parameter | Type | Description |
|---|---|---|
value | any | The value to deep-clone. Throws DataCloneError for unsupported types. |
transfer | Transferable[] | Optional. An array of transferable objects to move (not copy) into the clone. |
Return Value
A deep clone of value. The clone is a completely independent copy.
Core API
Basic Deep Cloning
// Nested objects — clone is fully independent
const config = {
database: {
host: "localhost",
port: 5432,
options: { ssl: true, timeout: 30 },
},
features: ["auth", "cache"],
};
const backupConfig = structuredClone(config);
backupConfig.database.host = "backup.example.com";
backupConfig.features.push("monitoring");
console.log(config.database.host); // localhost (unchanged)
console.log(config.features); // ["auth", "cache"] (unchanged)
Transfer Option — O(1) Ownership Transfer
The transfer option moves (rather than copies) Transferable objects. This is critical for performance with large binary data:
// Clone an object and transfer its large ArrayBuffer — no memory copy
const largeBuffer = new ArrayBuffer(50 * 1024 * 1024); // 50 MB
const data = { buffer: largeBuffer, metadata: { type: "image" } };
const clone = structuredClone(data, { transfer: [largeBuffer] });
// Original buffer is now detached — can no longer read it
console.log(largeBuffer.byteLength); // 0 (detached)
console.log(clone.buffer.byteLength); // 52428800 (50 MB)
console.log(clone.metadata.type); // image
Transferable types include: ArrayBuffer, MessagePort, ReadableStream, WritableStream, TransformStream, OffscreenCanvas, ImageBitmap.
Circular References
structuredClone() handles circular references correctly — something that trips up both JSON.stringify() and many manual deep-clone implementations:
const node = { value: 1 };
const parent = { child: node };
node.parent = parent; // circular!
// JSON.stringify(parent); // throws TypeError
const clone = structuredClone(parent);
console.log(clone.child.parent === clone); // true — circular preserved
console.log(clone === parent); // false — independent copy
Supported Types
Primitives — NaN, Infinity, and BigInt Included
// All primitives work — including ones JSON cannot represent
console.log(structuredClone(NaN)); // NaN (JSON → null)
console.log(structuredClone(Infinity)); // Infinity (JSON → null)
console.log(structuredClone(undefined)); // undefined (JSON → omitted)
console.log(structuredClone(42n)); // 42n (JSON → throws)
Date
const original = { created: new Date("2024-01-15"), tags: ["js"] };
const clone = structuredClone(original);
console.log(clone.created instanceof Date); // true
console.log(clone.created.toISOString()); // 2024-01-15T00:00:00.000Z
// JSON round-trip: clone.created would be a string, not a Date
RegExp
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/gi;
pattern.lastIndex = 10;
const clone = structuredClone(pattern);
console.log(clone.source); // (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
console.log(clone.flags); // gi
console.log(clone.lastIndex); // 0 — lastIndex is reset on the clone
// JSON round-trip: /regex/ becomes {}
Map and Set
const state = new Map([
["users", new Set(["alice", "bob", "carol"])],
["config", new Map([["theme", "dark"], ["lang", "en"]])],
]);
const clone = structuredClone(state);
console.log(clone instanceof Map); // true
console.log(clone.get("users") instanceof Set); // true
console.log(clone.get("users").has("alice")); // true
// JSON round-trip: Map → {}, Set → {} (all data lost)
ArrayBuffer and TypedArray
// Clone typed arrays — underlying buffer is deep-copied
const matrix = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
const clonedMatrix = structuredClone(matrix);
clonedMatrix[0] = 99;
console.log(matrix[0]); // 1 (original unchanged)
console.log(clonedMatrix[0]); // 99
// All TypedArray types are supported:
// Int8Array, Uint8Array, Uint8ClampedArray
// Int16Array, Uint16Array, Int32Array, Uint32Array
// Float32Array, Float64Array, BigInt64Array, BigUint64Array
Error Objects
const original = new TypeError("validation failed", {
cause: new RangeError("value out of bounds"),
});
const clone = structuredClone(original);
console.log(clone instanceof TypeError); // true
console.log(clone.message); // validation failed
console.log(clone.cause instanceof RangeError); // true
console.log(clone.cause.message); // value out of bounds
Unsupported Types
These types throw a DataCloneError or lose data when cloned:
| Type | Behavior | Reason |
|---|---|---|
Function | Throws DataCloneError | Cannot serialize executable code |
| DOM nodes | Throws DataCloneError | Platform-specific native objects |
Symbol value | Throws DataCloneError | Symbols are unique, cannot be recreated |
| Symbol-keyed properties | Silently dropped | Not enumerable to the algorithm |
WeakMap / WeakSet | Throws DataCloneError | Contents not enumerable by design |
WeakRef | Throws DataCloneError | Cannot serialize weak references |
| Class instances | Prototype lost | Only own enumerable data is cloned |
| Getters / Setters | Value captured | Accessor descriptors cannot be serialized |
Why WeakMap and WeakSet Throw DataCloneError
WeakMap and WeakSet cannot be cloned because their entries are not enumerable — that’s by design. A WeakMap holds its keys weakly so the garbage collector can reclaim them when no other reference exists. The structured clone algorithm cannot iterate over WeakMap entries to copy them, so it throws DataCloneError.
const wm = new WeakMap();
const key = { id: 1 };
wm.set(key, "secret");
try {
structuredClone(wm); // throws DataCloneError
} catch (e) {
console.log(e.name); // DataCloneError
console.log(e.message); // "WeakMap object could not be cloned"
}
Workaround: If you need to clone data that includes WeakMap-like behavior, extract the entries you need into a regular Map before cloning, then reconstruct the WeakMap from the cloned Map.
Custom Class Instances and Prototype Preservation
structuredClone() does not preserve the prototype chain. If you clone a class instance, the result is a plain Object — instanceof checks will fail, and prototype methods will be unavailable.
class User {
constructor(name) { this.name = name; }
greet() { return `Hi, I'm ${this.name}`; }
}
const alice = new User("Alice");
const clone = structuredClone(alice);
console.log(clone.name); // "Alice" — data is copied
console.log(clone instanceof User); // false — prototype lost!
console.log(clone.greet); // undefined — methods gone
Workaround: Implement a clone() method on your class, or use a serialization pattern:
class User {
constructor(name) { this.name = name; }
clone() { return new User(this.name); }
toJSON() { return { name: this.name }; }
static fromJSON(data) { return new User(data.name); }
}
Handling Uncloneable Data
// Option 1: strip functions before cloning
const { handler, ...cloneable } = appState;
const stateClone = structuredClone(cloneable);
// Option 2: wrap in try/catch
function safeClone(value, fallback = null) {
try {
return structuredClone(value);
} catch {
return fallback;
}
}
// Option 3: toJSON() / fromJSON() pattern for class instances
class Point {
constructor(x, y) { this.x = x; this.y = y; }
toJSON() { return { x: this.x, y: this.y }; }
static fromJSON(obj) { return new Point(obj.x, obj.y); }
clone() { return Point.fromJSON(structuredClone(this.toJSON())); }
}
const p = new Point(3, 4);
const pClone = p.clone();
console.log(pClone instanceof Point); // true
console.log(pClone.x); // 3
structuredClone() vs JSON.parse/JSON.stringify
| Feature | structuredClone() | JSON.parse/stringify |
|---|---|---|
Date | Preserved as Date | Converted to string |
Map / Set | Fully preserved | Converted to {} |
RegExp | Pattern and flags preserved; lastIndex resets | Converted to {} |
undefined | Preserved | Stripped from objects |
NaN / Infinity | Preserved | Converted to null |
BigInt | Preserved | Throws |
ArrayBuffer | Deep-copied | Throws |
| Circular references | Supported | Throws |
| Functions | Throws DataCloneError | Silently stripped |
| Output type | Value | JSON string |
| Performance | Faster for complex objects | Faster for simple objects |
Rule of thumb: Use structuredClone() for cloning data in memory. Use JSON.parse/stringify when you need a portable string representation or must support very old environments.
Use Cases
Immutable State Management
// Create an independent snapshot before mutation
function updateUser(state, id, updates) {
const draft = structuredClone(state);
const user = draft.users.find(u => u.id === id);
if (user) Object.assign(user, updates);
return draft; // original state unchanged
}
const state = { users: [{ id: 1, name: "Alice", role: "admin" }] };
const newState = updateUser(state, 1, { role: "moderator" });
console.log(state.users[0].role); // admin (unchanged)
console.log(newState.users[0].role); // moderator
Worker Communication
// structuredClone() uses the same algorithm as postMessage()
// Testing locally before sending to a Worker:
function sendToWorker(worker, data) {
try {
structuredClone(data); // verify it is cloneable
worker.postMessage(data);
} catch (e) {
console.error("Data not transferable:", e.message);
}
}
Deep Default Merging
function withDefaults(options, defaults) {
return { ...structuredClone(defaults), ...structuredClone(options) };
}
const defaults = {
timeout: 5000,
retry: { count: 3, delay: 1000 },
headers: new Map([["Content-Type", "application/json"]]),
};
const opts = withDefaults({ timeout: 10000 }, defaults);
console.log(opts.timeout); // 10000
console.log(opts.retry.count); // 3
console.log(opts.headers instanceof Map); // true
opts.retry.delay = 2000;
console.log(defaults.retry.delay); // 1000 (unchanged)
Frequently Asked Questions
Is structuredClone() faster than JSON.parse/JSON.stringify?
For deeply nested structures or structures containing complex types (Map, Set, Date, ArrayBuffer), yes — structuredClone() is typically faster because it avoids string serialization and parsing. For simple flat plain-object data, the performance difference is negligible. The more important consideration is correctness: structuredClone() is correct for all supported types, while the JSON round-trip silently corrupts data.
Can I deep-clone a class instance with structuredClone()?
You can pass a class instance, but the clone will be a plain Object — it loses its prototype, so instanceof checks fail and prototype methods are unavailable. To clone class instances with prototype intact, implement a clone() method on the class or use a serialization/deserialization pattern (toJSON() + fromJSON()).
What happens to non-enumerable properties?
They are silently dropped. structuredClone() only copies own enumerable string-keyed properties. Use Object.getOwnPropertyDescriptors() and Object.defineProperties() if you need to preserve non-enumerable properties.
Can structuredClone() be used in a Service Worker?
Yes. structuredClone() is available in all worker contexts including Service Workers, Web Workers, and Shared Workers. It shares the same algorithm used by postMessage().
How do I deep-clone a DOM node?
Use Node.cloneNode(true) for a deep clone (with all child nodes) or Node.cloneNode(false) for a shallow clone. structuredClone() throws a DataCloneError for DOM nodes.
Does structuredClone() clone the prototype?
No. The prototype chain is not preserved. Custom class instances become plain objects. If you need prototype preservation, use Object.assign(Object.create(Object.getPrototypeOf(src)), src) for shallow cloning with prototype.