Use structuredClone for Safe Deep Copying in JavaScript
Copying JavaScript values is easy until nested objects, dates, maps, sets, binary data, or circular references appear. The common JSON round trip works only for a limited subset of values and silently changes some data.
The built-in structuredClone() API provides a defined deep-cloning algorithm for many JavaScript data types.
Spread syntax is only a shallow copy
Object spread copies the first level:
const original = {
profile: { name: "Ada" }
};
const copy = { ...original };
copy.profile.name = "Grace";
console.log(original.profile.name); // "Grace"
Both objects still reference the same nested profile. Spread syntax is useful when shallow copying is intentional, but it is not a general deep-copy mechanism.
JSON cloning is a serialization trick
This pattern is common:
const copy = JSON.parse(JSON.stringify(value));It can be appropriate when data is intentionally JSON-shaped. It is not a transparent clone of arbitrary JavaScript values.
JSON serialization does not preserve structures such as Map, Set, typed arrays, circular references, or undefined properties in ordinary objects. Dates become strings rather than remaining Date instances.
If the boundary is genuinely JSON, serialize to JSON. If the goal is to clone an in-memory value, use a cloning API.
Basic structured cloning
Modern browsers and current Node.js releases provide structuredClone():
const original = {
createdAt: new Date(),
labels: new Set(["stable", "public"]),
metadata: new Map([["retries", 2]])
};
const copy = structuredClone(original);
console.log(copy.createdAt instanceof Date); // true
console.log(copy.labels instanceof Set); // true
console.log(copy.metadata instanceof Map); // true
Supported nested values are cloned recursively, so later mutations to the clone do not modify the original object graph.
Circular references are supported
The structured clone algorithm tracks references:
const node = { name: "root" };
node.self = node;
const copy = structuredClone(node);
console.log(copy !== node);
console.log(copy.self === copy);A JSON round trip cannot represent this graph.
Not every value is cloneable
Functions are not cloneable data:
structuredClone({ run() {} });This throws a cloning error. Other host-specific values may also be unsupported.
That failure is often useful design feedback. Objects containing behavior, open resources, runtime handles, or framework internals may need an application-specific reconstruction strategy rather than generic deep copying.
Transfer large buffers when ownership can move
Some transferable objects can be moved rather than copied:
const buffer = new ArrayBuffer(1024 * 1024);
const moved = structuredClone(buffer, {
transfer: [buffer]
});
console.log(buffer.byteLength); // 0
console.log(moved.byteLength); // 1048576
Transferring can avoid a byte-for-byte copy when moving large binary payloads between execution contexts. The trade-off is ownership: the original buffer becomes detached.
Deep copying has a cost
A deep clone walks an object graph and allocates new data. Repeating that on large structures creates CPU and garbage-collection pressure.
Before cloning defensively, consider whether the design would be clearer with:
- immutable updates that copy only changed paths;
- explicit ownership rules;
- a smaller projection of the fields a consumer needs;
- transfer semantics for large binary data.
Common pitfalls
Cloning to hide unclear ownership
If nobody knows which component may mutate an object, copying everywhere masks the underlying contract problem.
Expecting arbitrary class behavior to survive
Structured cloning copies supported data; it is not a universal constructor mechanism for application classes.
Choosing JSON because test fixtures are simple
Fixtures containing only strings and numbers do not expose the lossy behavior of JSON serialization. Test with real production-shaped values.
Conclusion
Use structuredClone() when you need an independent deep copy of supported JavaScript data. Use JSON serialization for JSON boundaries, spread syntax for shallow copies, transfer lists when ownership of large buffers can move, and explicit domain copying when application semantics matter. The useful question is not how to clone everything, but which data actually needs independent ownership.