structuredClone()
structuredClone(value) The structuredClone() function creates a deep copy of a JavaScript value using the structured clone algorithm. Unlike JSON.parse(JSON.stringify()), it handles complex data types including Maps, Sets, Dates, RegExps, and circular references.
Syntax
structuredClone(value)
structuredClone(value, options)
Parameters
| Parameter | Type | Description |
|---|---|---|
value | any | The object to be cloned. Can be any structured-cloneable type. |
options | object | Optional. An object with a transfer property containing an array of transferable objects. |
Transfer Options
The transfer option moves (rather than clones) specified transferable objects, making the original unusable. This enables zero-copy data transfer between contexts like web workers or IndexedDB. Transfer is useful when you need to hand ownership of a buffer to another context without copying its bytes — for example, sending a large ArrayBuffer from the main thread to a worker:
const buffer = new ArrayBuffer(16);
const cloned = structuredClone({ buffer }, { transfer: [buffer] });
// buffer.byteLength is now 0
Transferable objects include ArrayBuffer, TypedArray, MessagePort, and ImageBitmap. When you transfer an object, the original becomes detached — its byte length drops to zero and attempts to read or write its contents will fail. You can verify a successful transfer by checking byteLength on the original buffer, which will be 0 after the operation.
Return Value
A deep copy of the original value. If the value contains circular references, they are preserved in the clone.
Examples
Basic Clone
const original = {
name: "Alice",
data: [1, 2, 3],
metadata: new Map([["key", "value"]])
};
const clone = structuredClone(original);
clone.name = "Bob";
clone.data.push(4);
console.log(original.name); // "Alice"
console.log(clone.name); // "Bob"
console.log(original.data); // [1, 2, 3]
console.log(clone.data); // [1, 2, 3, 4]
The clone is fully independent — mutating either the original or the copy does not affect the other. Beyond basic objects, structuredClone() preserves circular references, which are common in graph-like data structures such as DOM trees and linked lists. Circular references would cause JSON.stringify() to throw a TypeError, so this capability is one of the primary reasons to choose structuredClone() over JSON-based approaches. The following example creates a self-referential object and verifies that the circular link survives the clone:
Cloning with circular references
const original = { name: "circular" };
original.self = original;
const clone = structuredClone(original);
console.log(clone.name); // "circular"
console.log(clone.self === clone); // true
The circular reference case shows that structuredClone() handles self-references correctly, but a different concern arises with binary data where cloning large buffers wastes memory. The transfer option addresses this by moving ownership instead of copying:
Transferring ArrayBuffers
Transfer moves ownership of a buffer rather than duplicating its contents. After the transfer completes, the source object becomes detached and can no longer be used for read or write operations. This is particularly valuable when passing large binary payloads between workers, where copying would add unnecessary latency and memory pressure:
const uInt8Array = new Uint8Array([1, 2, 3]);
const transferred = structuredClone(uInt8Array, {
transfer: [uInt8Array.buffer]
});
console.log(uInt8Array.byteLength); // 0
console.log(transferred); // Uint8Array [1, 2, 3]
Error Handling
structuredClone() throws a DataCloneError when it encounters values that the structured clone algorithm cannot serialize. Functions, DOM nodes, and WeakMap/WeakSet entries are the most common triggers. The error surfaces immediately rather than silently dropping data, which is one advantage over JSON-based approaches that quietly discard unsupported types:
const obj = { fn: () => {} };
try {
structuredClone(obj);
} catch (e) {
console.log(e.name); // "DataCloneError"
console.log(e.message); // "...could not be cloned."
}
The DataCloneError is the standard failure mode for unsupported values. Rather than silently dropping data like JSON-based methods, structuredClone() surfaces the issue immediately, which helps catch serialization problems during development. Beyond individual value types, several broader categories of JavaScript constructs are also incompatible with the structured clone algorithm.
Limitations
structuredClone() cannot clone:
- Functions — throws
DataCloneError - DOM nodes — throws
DataCloneError - Property descriptors, setters, and getters — metadata is lost
- RegExp.lastIndex — the property is not preserved
- Class private fields
- Prototype chain — only own enumerable properties are copied
- WeakMap and WeakSet — throws
DataCloneError
Most of these limitations follow from the fact that the structured clone algorithm operates on data, not on behaviour or identity. Functions and DOM nodes carry execution context that cannot be meaningfully transferred between realms. Property descriptors and prototypes are metadata that the algorithm deliberately ignores, producing a plain data snapshot instead of a faithful mirror of the original object structure.
Symbol Behavior
Symbol handling has two distinct behaviors. Primitive Symbol values cannot be cloned and throw DataCloneError, but Symbol property keys on objects are preserved in the clone. This means you can use Symbols as unique property identifiers and expect them to survive a structured clone:
const sym = Symbol("test");
const obj = { [sym]: "value", regularKey: "data" };
const clone = structuredClone(obj);
console.log(Object.getOwnPropertySymbols(clone)); // [Symbol(test)]
console.log(clone[sym]); // "value"
Error Objects
Standard error types (Error, TypeError, RangeError, etc.) are cloned correctly — their name, message, and stack properties all survive the operation. Custom error subclasses, however, lose their constructor identity. The structured clone algorithm forces the name property back to "Error", which means type-specific error handling based on instanceof checks will fail on the cloned copy:
class CustomError extends Error {
constructor(message) {
super(message);
this.name = "CustomError";
}
}
const error = new CustomError("oops");
const cloned = structuredClone(error);
console.log(cloned.name); // "Error" — name is forced to "Error"
When structuredClone beats JSON tricks
structuredClone() is the better choice when the value contains anything more complex than plain JSON data. Maps, Sets, Dates, typed arrays, and circular structures all survive the clone in a way that JSON-based copying cannot match. That makes it useful for state snapshots, worker communication, and data copies where you want the same shape back on the other side.
The key benefit is correctness. A JSON round-trip quietly drops information, while structuredClone() either preserves the supported structure or throws a clear error for unsupported values. That is a cleaner failure mode when you care about the integrity of the copy.
Transferable values and limits
The transfer option is a special case worth remembering. When you transfer an ArrayBuffer or another transferable object, ownership moves instead of being copied. That can save memory and time, but it also means the original object is no longer usable in the same way after the transfer.
Even with that flexibility, structuredClone() is still not a universal serializer. Functions, DOM nodes, and custom prototype behavior are outside its model. Treat it as a deep-copy tool for structured data, not as a way to duplicate every possible JavaScript value.
See Also
- JSON.parse() — parse JSON strings
- JSON.stringify() — convert values to JSON
- Object.assign() — shallow copy and merge objects