Why copying an object is harder than it looks
Assigning an object doesn't copy it — const b = a just makes b another reference to the same object, so mutating b.user.name also changes a. To get an independent copy you need a deep clone: a brand-new object tree with no shared references. For years the go-to trick was a JSON round-trip, and it works just often enough to hide its sharp edges. Modern JavaScript has a proper answer — structuredClone — and knowing the difference prevents a whole class of silent data-corruption bugs.
The JSON round-trip clone
The classic one-liner serializes to a JSON string and parses it back:
const copy = JSON.parse(JSON.stringify(original));Because JSON.stringify walks the whole tree and JSON.parse rebuilds a fresh one, the result is genuinely deep — no shared references. For a plain object of strings, numbers, booleans, arrays, and nested objects, it's perfectly fine and still widely used.
The problem is everything JSON *can't represent*. The round-trip doesn't just fail loudly — it silently changes your data:
- Dates become strings.
JSON.stringifyturns aDateinto an ISO string, andJSON.parseleaves it as a string. Your clone'screatedAtis now text, not a Date — and nothing warned you. (See serializing dates in JSON.) - `undefined` and functions vanish. Object properties set to
undefinedor a function are dropped entirely. A field silently disappears from the clone. - `undefined` in arrays becomes `null`. Array holes don't survive as
undefined. - `Map`, `Set`, `RegExp` are mangled. A
MaporSetbecomes{}; aRegExpbecomes{}. All their contents are lost. - `NaN` and `Infinity` become `null`.
- BigInt throws.
JSON.stringifythrows on aBigIntoutright. - Circular references throw. Any object that references itself produces
TypeError: Converting circular structure to JSON.
For a config object of primitives, none of this bites. For anything with dates, maps, or cycles, the round-trip quietly corrupts the copy.
structuredClone: the native deep clone
structuredClone is a built-in function (browsers and Node 17+) that deep-clones using the structured clone algorithm — the same mechanism the platform uses to pass data to Web Workers. It exists precisely to fix the JSON round-trip's gaps:
const copy = structuredClone(original);It correctly preserves the types JSON destroys:
- Dates stay Dates,
RegExpstaysRegExp. - `Map` and `Set` clone with their contents intact.
- Circular references just work — a self-referencing object is cloned with the cycle preserved, no throw.
- Typed arrays, `ArrayBuffer`, `Blob`, `File` and many other built-ins are supported.
- `BigInt`, `NaN`, `Infinity` all survive.
For most real-world objects, structuredClone is simply the correct default now.
What structuredClone still can't do
It's not magic — the structured clone algorithm has its own exclusions, and it *does* throw (a DataCloneError) rather than corrupt silently, which is the better failure mode:
- Functions can't be cloned — passing an object containing a function throws.
- DOM nodes (most of them) can't be cloned.
- Prototypes are not preserved. A class instance is cloned as a plain object with the same properties but not the same prototype chain — so it won't be an
instanceofyour class. - Property descriptors (getters/setters, non-enumerable flags) are not carried over.
Choosing a clone strategy
| Need | Use |
|---|---|
| Plain primitives/arrays/objects | structuredClone (or the JSON trick if you like) |
| Dates, Maps, Sets, cycles, typed arrays | `structuredClone` |
| Objects containing functions | A library like lodash cloneDeep, or clone manually |
| Preserve class instances / prototypes | Custom clone, or a class clone() method |
| Just need a fast shallow copy | Spread { ...obj } or Object.assign |
The short version: default to `structuredClone`, drop to a library like lodash's cloneDeep when you need to clone functions or class instances, and only use JSON.parse(JSON.stringify(x)) when you already know the data is pure JSON-safe primitives. The JSON trick isn't wrong — it's just a clone that quietly assumes your data looks exactly like JSON, which real objects often don't.
For the serialization details behind the round-trip, see the JSON.stringify guide and null vs undefined handling. To inspect a cloned structure, format it.