What does structuredClone(value) give you that a JSON.parse(JSON.stringify(value)) round trip does not, and which values does it refuse to clone?
answer
- one is a real copy, one is a serialisation
- cycles and shared references survive
- behaviour cannot be copied
- the prototype does not come along
- it throws instead of dropping
basics
~20 sstructuredClone performs a real deep copy: it preserves cycles and shared references and reproduces Date, RegExp, Map, Set, ArrayBuffer and typed arrays as themselves. It throws a DataCloneError for functions and symbols, and it does not preserve prototypes.
solid answer
~50 sA JSON round trip is a serialisation, not a copy: it only knows the JSON data model, so `Date` becomes a string, `Map` and `Set` become `{}`, `undefined` and function-valued properties vanish, and a cycle throws. `structuredClone` implements the structured clone algorithm, which walks the object graph directly. It reproduces `Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, typed arrays and `Error` objects as the same kinds of things, it preserves cycles, and it preserves shared references — if two properties pointed at one object, the clone's two properties point at one clone. What it will not do is carry behaviour: a function or symbol anywhere in the graph makes the whole call throw a `DataCloneError`, and a class instance comes back as a plain object with the same data but no prototype, so its methods and `instanceof` are gone.
code
javascript · 24 linesconst shared = { id: 1 };
const state = {
when: new Date('2020-01-01'),
tags: new Set(['a', 'b']),
by: new Map([['k', 1]]),
left: shared,
right: shared,
};
state.self = state;
const copy = structuredClone(state);
console.log(copy.when instanceof Date); // true
console.log(copy.tags.has('a')); // true
console.log(copy.by.get('k')); // 1
console.log(copy.self === copy); // true
console.log(copy.left === copy.right); // true
console.log(copy.left === shared); // false
try {
structuredClone({ run() {} });
} catch (e) {
console.log(e.name); // "DataCloneError"
}go deeper
Know that structuredClone is the built-in deep copy and that the JSON round trip is not one: dates, Maps and Sets do not survive it, and cycles make it throw.
Be ready to list what the clone algorithm reproduces, explain that cycles and shared references are preserved, and state precisely what happens to a function in the graph and to a class instance's prototype.
Demonstrate judgment about where copies belong at all — which boundaries need a defensive snapshot, and how to shape state so that data is cloneable and behaviour lives elsewhere.
Own the data-shape rule for the codebase: state that crosses a clone, a worker or a storage boundary must be plain serialisable data, with rehydration into rich objects as an explicit, tested step rather than an assumption.
## The two things people mean by "copy" A **shallow** copy (`{ ...obj }`, `Object.assign({}, obj)`) makes a new outer object whose properties still point at the same nested objects. A **deep** copy makes new objects all the way down, so mutating the copy cannot be observed through the original. The JavaScript standard library had no deep-copy function for a long time, which is why `JSON.parse(JSON.stringify(x))` became folklore. ## Why the JSON round trip is not a deep copy It is a *serialisation followed by a parse*, and it can only express what the JSON data model can express. Going through it, at the level that matters for copying: - values with no JSON representation — `undefined`, functions, symbols — are dropped from objects and become `null` inside arrays; - a `Date` comes back as a string, because serialising it produced a string; - `Map`, `Set`, `RegExp` and class instances come back as ordinary objects, usually empty ones, because their state is not in enumerable own properties; - `BigInt` throws; - a cycle throws, because there is no way to write one down. So the round trip works only for graphs that were already plain JSON-shaped data, and fails *silently* for most of the interesting cases — which is what makes it dangerous rather than merely limited. ## What structuredClone does instead `structuredClone(value)` runs the **structured clone algorithm**, which walks the live object graph and builds a parallel graph of the same kinds of objects. Types it reproduces as themselves include `Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, `DataView`, the typed arrays, and `Error` objects; in a browser it also handles host types marked serializable, such as `Blob`, `File` and `ImageData`. It handles graph shape, not just tree shape: ```js const state = { when: new Date(), tags: new Set(['a']), by: new Map([['k', 1]]) }; state.self = state; // a cycle const shared = { id: 1 }; state.left = shared; state.right = shared; // two paths to one object const copy = structuredClone(state); copy.when instanceof Date; // true copy.tags.has('a'); // true copy.self === copy; // true — cycle preserved, not infinite copy.left === copy.right; // true — sharing preserved copy.left === shared; // false — but it is a new object ``` That cycle-and-sharing preservation is the property a hand-written recursive clone usually gets wrong, and it is the strongest single reason to prefer the built-in. ## What it refuses: DataCloneError The algorithm has no way to copy behaviour, so it does not try. A function or a symbol anywhere in the graph makes the call throw a `DataCloneError` (a `DOMException`), and the throw is **all-or-nothing**: you do not get a partial clone with the offending property omitted. ```js structuredClone({ n: 1, onDone() {} }); // DataCloneError: onDone() {} could not be cloned. ``` Browser DOM nodes are likewise not cloneable and throw. Note that this differs from `JSON.stringify`, which drops functions quietly — structuredClone's loudness is a feature: it tells you the value you were about to copy contained something a copy cannot represent. ## What it changes silently The important silent transformation is **prototypes are not preserved**. Cloning an instance of your own class produces a plain object carrying the same own data properties, with `Object.prototype` as its prototype: ```js class User { constructor(name) { this.name = name; } greet() { return `hi ${this.name}`; } } const clone = structuredClone(new User('ada')); clone.name; // 'ada' clone instanceof User; // false clone.greet; // undefined ``` In the same vein, accessor properties are read and stored as plain data, and property attributes such as non-enumerable or non-writable are not carried across. Private class fields (`#x`) are not own properties at all, so they do not survive either. `structuredClone` also ignores `toJSON()` — that hook belongs to JSON serialisation and has no role in the clone algorithm. ## Where it comes from and where it is available `structuredClone` is a global function defined by the HTML specification rather than by ECMAScript, which is why you will not find it in a language reference under `Object`. The underlying algorithm has been in the platform for years as the mechanism behind `postMessage` and IndexedDB storage; exposing it as a callable function is the recent part. It is available in evergreen browsers from early 2022 and in Node from version 17. ## How to decide Use `structuredClone` when you need a deep copy of *data* — state snapshots for undo, defensive copies before a mutating routine, detaching a value from a reactive store. Use a manual or library clone when the graph contains behaviour or when you must preserve class identity. Use `JSON.parse(JSON.stringify(x))` essentially never as a clone: if the data really is JSON-shaped, structuredClone copies it correctly too and faster; if it is not, the JSON round trip corrupts it quietly.
- Two properties of an object point at the same nested object. What is true of them after structuredClone?They still point at one object — a single new clone reachable from both paths. The algorithm keeps a memory of what it has already cloned, which is the same mechanism that lets it terminate on cycles. A JSON round trip loses this: each path is serialised independently, so the parse produces two separate objects with equal contents.
- Does structuredClone use toJSON() the way JSON.stringify does?No. `toJSON()` is a hook of the JSON serialisation algorithm only. The structured clone algorithm never calls it, so a class that relies on `toJSON()` to flatten itself for the wire gets no such treatment here — its own data properties are copied and its prototype is dropped.
- Why is structuredClone throwing on a function arguably better than JSON.stringify silently dropping it?Because a dropped function turns into a bug far from its cause: something later calls `copy.onDone()` and gets a TypeError with no hint that a copy removed it. Throwing at the copy site names the offending property immediately, and it forces an explicit decision — restructure the value so data and behaviour are separate, or clone it some other way.
saying these in an interview costs you the question
- Says JSON.parse(JSON.stringify(x)) is a proper deep copy
- Expects class methods or instanceof to survive a clone
- Thinks a cycle causes structuredClone to hang or overflow
- Assumes non-cloneable properties are skipped rather than thrown on
- Believes structuredClone respects toJSON()