Lesson 0001 · ~10 minutes
One skill: place structured clone on the copy / serialize spectrum and pick it only when the hard problem is a faithful deep copy of structured values — not a JSON document, not a shallow spread, not class revival.
After the quiz, you can defend “JSON / structuredClone / shallow /
transfer / custom” in a design review — and stop treating
JSON.parse(JSON.stringify(x)) as a deep clone.
In
Workers lesson 3, structured clone was the default message tax:
postMessage copies; transfer moves ownership; functions and
DOM nodes throw
DataCloneError. BroadcastChannel uses the same algorithm with
no transfer list. IndexedDB stores values that survive
that algorithm.
This track owns the algorithm as a
first-class tool: what it is, when to call
structuredClone()
yourself, and when a different copy tool is the honest choice.
The
HTML Standard
calls the serialize + deserialize pair
structured cloning. Most APIs split those steps (store now, restore later; send here,
receive there).
structuredClone() is the exception: both steps, same
realm, one call.
JavaScript assignment copies a reference. Surma (web.dev) splits the rest into two jobs people confuse:
{...obj}, Object.assign,
array.slice()) — new outer object, same nested
objects. Edit copy.nested.x and the original changes too.
You need a deep copy when two pieces of code must not accidentally share state: a form draft vs the store, a worker payload you will keep mutating, a History entry that must not alias live UI state.
Until 2022 you faked that with JSON round-trip or a library. The platform now exposes the same algorithm it already used internally:
const snapshot = structuredClone(draft);
MDN: baseline widely available (Chrome 98+, Firefox 94+, Safari 15.4+). In 2026 that is the default — not a polyfill conversation.
Two confusions that cause bad design reviews:
JSON.stringify
turns Date into an ISO string,
Map/Set into {}, drops
undefined keys, and throws on cycles and
BigInt. Functions disappear silently (or become
null in arrays). Structured clone keeps
Date/Map/Set/cycles and
throws DataCloneError on functions and DOM
nodes. Loud failure beats silent loss — unless you
want a JSON document for a server or file.
structuredClone.
Surma’s article is the right decision primary, but two details
aged: JSON usually silently loses Map/Set/Date
rather than throwing, and Error is now cloneable per MDN.
When in doubt, WHATWG + current MDN win.
New outer object; nested refs stay shared. Right when you only replace top-level fields.
You need a JSON document (HTTP, file, log). Accept or design around loss. Not a deep-clone substitute.
Faithful deep copy of structured data: cycles, Dates, Maps,
buffers, many Web types. Explicit:
structuredClone(). Implicit: postMessage, BC, IDB,
History.
Move ownership of a transferable (you already know this from workers). Sender is detached.
Methods, class identity, schema versions, stable cross-language
wire. Structured clone will not revive your Invoice
class.
If the hard part is
“this same-realm value must become an independent nested copy”,
call structuredClone(). If a platform API
will already clone (worker message, IDB put, pushState),
pass data through —
don’t double-clone.
If the hard part is
“a server or file must parse JSON”, use
JSON and design for what it drops. If you only need a
new outer object, shallow. If you need methods or a
versioned domain type, custom serializer. Large binary
ownership → transfer.
| Signal | Leans structured clone | Leans away |
|---|---|---|
| Job | Independent deep copy of structured data | JSON document, shared nested refs, or class revival |
| Who clones |
You call structuredClone(), or an API already does
|
You structuredClone and then postMessage the copy for no reason |
| Types | Dates, Maps, Sets, cycles, buffers, Blobs | Functions, DOM nodes, live class instances |
| Failure mode | Throws DataCloneError (loud) |
JSON silent drop / stringify TypeError |
| Classic fits | Detach a draft from a store; snapshot before mutate; History state; IDB / worker payloads | REST body, protobuf file, “clone this Vue/React proxy with methods” |
structuredClone(x) is one line. What bites later: reactive
proxies (Vue/MobX) that throw
DataCloneError, clone
cost on huge graphs (Workers lesson 3), transfer
detachment surprises, and thinking a clone preserved
methods. Type matrix and transfer next. Today: pick the right row on
the spectrum.
Choose the best default. Equal-length options so formatting doesn’t hint. Feedback is immediate.
A modal must edit a nested editor draft. The store object, including
nested arrays and a Date, must stay untouched.
POST settings to your REST API. The server only accepts
application/json.
You copy a user to set name. Nested
prefs should stay the same object so both
sides see pref updates.
Persist a domain Invoice class (methods, private
fields, schema version) to a file other services must read for
years.
A worker should take ownership of a 16MB
ArrayBuffer of pixels. Main thread must not keep using
those bytes.
structuredClone() for a same-realm snapshot; let
postMessage / IDB / History clone when they already will.
Date/Map/Set; it is not a clone.
DataCloneError you’ve seen — ask in chat. Follow-up
questions are part of the method, not a distraction.
Surma — Deep-copying in JavaScript using structuredClone.
Best primary for this lesson’s job split (shallow vs JSON hack vs
platform deep copy). Then skim
MDN — Structured clone algorithm
for the type list you’ll own next lesson. Remember the two dated claims
in the 2021 article (JSON rarely throws on
Map/Date; Error now clones).