Given `const shared = { n: 1 }; const o = { a: shared, b: shared }; o.self = o;`, what does `structuredClone(o)` return, and what does that reveal about how the algorithm walks an object graph?
answer
- it copies a graph, not a tree
- two references in, two references out
- the algorithm remembers what it has seen
- registered before recursing, so cycles end
- identity holds inside one clone only
basics
~20 sThe clone reproduces the graph exactly: c.a === c.b is true and c.self === c is true, while c.a === shared is false. The algorithm keeps a map of already-serialized objects, so each source object becomes exactly one clone, which makes cycles and shared references safe.
solid answer
~40 sYou get a faithful copy of the *graph*, not a naive tree copy. `c.a === c.b` is `true` because `shared` was visited once and both properties point at that single clone; `c.self === c` is `true` because the cycle is reproduced as a cycle; and `c.a === shared` is `false` because nothing is shared with the original. The mechanism is a memory map maintained during serialization: the first time an object is encountered it is serialized and recorded, and every later encounter emits a back-reference to that record instead of recursing again. That is why the algorithm cannot infinitely recurse on a cycle, and it is the key structural difference from a text round-trip, where `JSON.stringify` throws on the cycle and would in any case duplicate `shared` into two independent objects.
code
javascript · 11 linesconst shared = { n: 1 };
const o = { a: shared, b: shared };
o.self = o;
const c = structuredClone(o);
console.log(c.a === c.b); // true - aliasing preserved
console.log(c.self === c); // true - cycle preserved
console.log(c.a === shared); // false - independent of the source
c.a.n = 99;
console.log(c.b.n, shared.n); // 99 1go deeper
Know that structuredClone handles a self-referencing object without blowing up, and that the copy is fully independent of the original — mutating the clone never affects the source.
Explain the memory map: each object is serialized once and later encounters become back-references, which is simultaneously why cycles terminate and why aliasing is preserved. Contrast with a naive recursive copier.
Recognise where graph fidelity actually matters — normalized state, parent back-pointers, shared nodes — and note that identity is preserved only within a single clone, so cross-message deduplication needs explicit ids.
Decide whether your cross-boundary payloads should be graphs at all. Normalized flat records with explicit ids travel predictably and diff cleanly; relying on preserved aliasing couples both sides to one algorithm's guarantees.
## What the snippet produces ```js const shared = { n: 1 }; const o = { a: shared, b: shared }; o.self = o; const c = structuredClone(o); c.a === c.b; // true — aliasing preserved c.self === c; // true — cycle preserved c.a === shared; // false — nothing shared with the source c.a.n = 99; shared.n; // 1 — the graphs are fully independent ``` Three separate facts are on display: the copy is deep, the copy is *isomorphic* to the original graph, and the copy shares no identity with it. ## The mechanism: a memory map The serialize step of the structured clone algorithm carries a map from each object it has already serialized to the record it produced. When it reaches an object, it first looks it up. On a hit it emits a reference to the existing record and stops; on a miss it creates the record, stores it in the map, and only then recurses into the object's contents. The deserialize step mirrors this with its own map from records to freshly created objects. Two consequences fall straight out of that design. A cycle terminates, because the second visit to `o` finds `o` already in the map. And aliasing is preserved, because two properties that referenced the same source object end up referencing the same record and therefore the same clone. The ordering detail matters: the record is registered *before* the contents are walked. If it were registered afterwards, a self-referencing object would recurse forever. ## Why this is not what a naive deep copy does A hand-rolled recursive copier usually looks like `if (typeof v === 'object') return Object.fromEntries(Object.entries(v).map(...))`. That version has neither property: it overflows the stack on `o.self = o`, and it turns one `shared` object into two independent copies, so writing through `c.a` no longer shows up through `c.b`. Getting it right means adding exactly the same memory map — usually a `WeakMap` from source object to clone — that the platform algorithm already has. A JSON round-trip does not even get the chance: `JSON.stringify` throws on the cycle, and if you break the cycle it still duplicates `shared` into two objects, silently changing the meaning of your data structure. ## When preserved aliasing actually matters Most application data is a tree, so the distinction goes unnoticed. It stops being academic when the graph is genuinely a graph: - normalized state where many records point at the same entity object - a document model with parent back-pointers - an adjacency structure, a linked list, or an undo stack whose entries reference shared nodes For those, structured cloning is one of the few copy mechanisms that keeps the shape intact — and that is worth knowing precisely because the same guarantee applies at every boundary the algorithm backs, so a graph posted to a worker or written to IndexedDB arrives with its sharing intact. ## The cost side of the same coin The map is also what bounds the work. Each *distinct* object in the graph is serialized once no matter how many times it is referenced, so the cost is proportional to the number of distinct nodes and their contents, not to the number of paths through the graph. A densely shared graph is therefore much cheaper to clone than its expanded tree form would suggest — a useful thing to know before assuming a big-looking structure will be slow. ## A caveat worth stating Identity is preserved *within* one clone operation, never *across* operations. Cloning the same object twice gives two unrelated results, and posting the same object in two separate `postMessage` calls delivers two independent copies on the receiving side. If the receiver needs to recognise "this is the same entity I already have", the identity has to be carried as data — an explicit id field — because object identity does not survive the boundary.
- Why does registering the object in the memory map before recursing into it matter?Because that ordering is what terminates a cycle. When the walk re-enters `o` while still serializing `o`, the lookup must already succeed and emit a back-reference. If registration happened after the contents were walked, the second visit would miss, recurse again, and never terminate.
- If I post the same object to a worker twice, does the worker see one object or two?Two. The memory map lives inside a single serialize operation, so identity is preserved within one clone and never across calls. The worker receives two independent copies with no relationship. To let the receiver deduplicate, carry an explicit id field in the data.
- Does preserving shared references change how expensive the clone is?Yes, favourably. Each distinct object is serialized once regardless of how many properties reference it, so cost tracks the number of distinct nodes rather than the number of paths. A heavily shared graph clones far more cheaply than its expanded tree equivalent would.
saying these in an interview costs you the question
- Says cycles cause infinite recursion or a stack overflow
- Expects two aliased properties to become two separate objects
- Thinks the clone shares references with the original
- Believes object identity survives across separate postMessage calls
- Assumes any recursive deep-copy function behaves the same way