Structured Clone Is More Than JSON with Better Type Support
What structuredClone preserves, what transfer changes, and where class instances lose their identity.
JSON.stringify and JSON.parse are often used as an accidental cloning API. That round trip loses undefined, cannot represent cycles, converts dates to strings, and fails on BigInt. The structured clone algorithm is designed for moving JavaScript data between realms and workers, and it handles a much wider set of values.
const source = {
createdAt: new Date(),
ids: new Set([1, 2, 3]),
};
source.self = source;
const clone = structuredClone(source);
console.log(clone.self === clone); // true
Maps, sets, dates, regular expressions, typed arrays, errors, and cyclic graphs are among the supported structures. Functions, DOM nodes, and weak collections cannot be cloned.
Graph identity is preserved within the clone
Structured clone recursively creates a new graph while remembering values already visited. If two source properties point to the same object, the corresponding clone properties point to one cloned object rather than two independent copies.
const shared = { ready: true };
const result = structuredClone({ left: shared, right: shared });
console.log(result.left === result.right); // true
console.log(result.left === shared); // false
This identity preservation is one of the important differences from hand-written recursive copying functions.
User-defined class instances keep their prototype
Structured clone reconstructs ordinary user-defined instances with their original prototype. Methods remain available because the clone’s prototype still points to the class’s prototype object, although private fields cannot be inspected by the cloning algorithm.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
length() {
return Math.hypot(this.x, this.y);
}
}
const copy = structuredClone(new Point(3, 4));
console.log(copy instanceof Point); // true
console.log(copy.length()); // 5
Property descriptors are not a serialization contract. Getters may be evaluated, non-enumerable properties can be absent, and copied data properties receive ordinary attributes. Clone data, not behaviour, when crossing a boundary.
Transfer changes ownership
Some objects can be transferred instead of cloned. The most common example is an ArrayBuffer sent to a worker:
const buffer = new ArrayBuffer(16 * 1024 * 1024);
worker.postMessage(
{ kind: 'pixels', buffer },
{ transfer: [buffer] },
);
console.log(buffer.byteLength); // 0
After a successful transfer, the source buffer is detached. Existing views can no longer access its bytes. The receiving side owns the transferred resource, avoiding the cost of duplicating a large allocation.
A SharedArrayBuffer can also appear in the transfer list. Unlike an ordinary buffer it remains usable by the sender, because both realms intentionally share the same memory. Cross-origin isolation requirements in browsers protect this feature.
Transfer lists must correspond to transferable values reachable from the cloned input. Listing the same transferable twice or trying to transfer an unsupported value raises a cloning error.
Make ownership explicit
Use plain structured clone when both sides need independent data. Use transfer when exactly one side should continue using an expensive resource. Use shared memory only when concurrency and synchronisation are part of the design.
The API’s strength is that these choices are visible at the call site. A transfer list documents a move, while an ordinary clone documents duplication. Neither should be mistaken for cloning arbitrary application objects with all of their behavioural identity intact.