Stack Patterns — Episode 12
You have written this line. You know you have.
const copy = JSON.parse(JSON.stringify(original));
It works. Until it does not. Your Date becomes an ISO string. Your Map becomes an empty object. Your undefined properties vanish without warning. Your RegExp becomes {}. NaN becomes null. Infinity becomes null. BigInt throws a TypeError. Circular references throw a TypeError. And you discover all of this in production, because the test data was flat, simple, and contained none of the types that the real data contained.
For over a decade, JavaScript had no native deep clone. The JSON.parse(JSON.stringify()) hack was the folk remedy: available everywhere, effective enough for simple cases, silently destructive for everything else. It works by serialising your object to a JSON string and parsing it back, which means anything that is not valid JSON is lost, transformed, or throws. This is not a deep clone. It is a deep clone of whatever your object happens to look like after being forced through a text format that was designed for data interchange, not object replication.
The professional alternative was Lodash's cloneDeep. At 17 KB minified (5.3 KB gzipped), it is a standalone package with 22 million weekly downloads in 2026. Its last published version, 4.5.0, is over ten years old. Twenty-two million weekly downloads for a function that has not been updated in a decade, solving a problem the platform has solved since 2022.
In March 2022, the platform did.
The Pattern
const copy = structuredClone(original);
One line. Zero dependencies. Zero kilobytes shipped to the client. It handles Date, RegExp, Map, Set, ArrayBuffer, SharedArrayBuffer, Blob, File, FileList, ImageData, CryptoKey, DOMException, DOMMatrix, DOMPoint, DOMRect, Error (including stack and cause), and circular references. Over thirty types that the JSON hack either destroys or throws on.
Available in Chrome 98 (February 2022), Firefox 94 (November 2021), Safari 15.4 (March 2022), Node.js 17 (October 2021), and Deno 1.14 (August 2021). It has been universally available for over four years. There is no polyfill concern. There is no browser support excuse.
The History
The structured clone algorithm has existed in the HTML specification since approximately 2010. It was used internally by postMessage() (for sending objects between windows and workers) and IndexedDB (for storing objects). For eleven years, the browser contained a complete, type-aware, cycle-safe deep cloning implementation and did not expose it as a public API.
In 2016, Surma filed WHATWG Issue #793 proposing that the structured clone algorithm be exposed as a global function. The resulting pull request (#3414) took three and a half years to review, iterate, and merge. It shipped in June 2021 and reached all major browsers by March 2022.
The function is defined in the WHATWG HTML Living Standard, not in ECMAScript. This is a web platform API, not a language feature. The distinction matters: it means structuredClone is available in browsers, Node.js, Deno, and Bun, but it is not part of the JavaScript language specification itself.
Why It Works
The JSON hack operates by serialising your object to a text representation and parsing it back. This round-trip through JSON means every type that JSON does not support is either silently dropped, silently transformed, or throws an error:
| Input | JSON.parse(JSON.stringify()) | structuredClone() |
|---|---|---|
new Date() |
ISO string (loses type) |
Date (preserved) |
new Map([[1,2]]) |
{} |
Map (preserved) |
new Set([1,2,3]) |
{} |
Set (preserved) |
/regex/gi |
{} |
RegExp (preserved) |
undefined |
property omitted |
undefined (preserved) |
NaN |
null |
NaN (preserved) |
Infinity |
null |
Infinity (preserved) |
BigInt(42) |
throws TypeError |
42n (preserved) |
| circular reference | throws TypeError | handled correctly |
new Blob(["hi"]) |
throws TypeError |
Blob (preserved) |
structuredClone operates on the object graph directly. It walks the structure, preserves types, tracks references to detect cycles, and produces a genuine copy. No serialisation round-trip. No type erasure. No silent data loss.
What It Does Not Do
structuredClone has honest constraints. It throws a DataCloneError (a DOMException) when it encounters something it cannot clone:
- Functions: throws immediately. Objects are data; functions are behaviour. The algorithm clones data.
-
DOM nodes: throws immediately. Cloning a DOM tree is
cloneNode(true), notstructuredClone. - Property descriptors: getters and setters are not duplicated. The result is a plain data copy.
- Prototype chain: discarded. Class instances become plain objects. If you need to preserve class identity, you need a custom approach.
- Symbols as keys: silently ignored (not cloned).
- WeakMap, WeakRef, WeakSet: not cloneable by design (they hold weak references).
These are honest limitations. The JSON hack has many of the same limitations but communicates them by silently returning wrong data. structuredClone throws an exception. One does rather prefer the tool that admits what it cannot do over the tool that pretends it did.
Performance
For small, simple objects with only JSON-safe types, the JSON hack can be faster (approximately 6x in microbenchmarks). This is because JSON.stringify and JSON.parse are highly optimised V8 built-ins, and the structured clone algorithm performs additional work (type checking, cycle tracking) that the JSON path skips.
For complex objects with mixed types, structuredClone performs comparably or better, and does not destroy your data in the process. One benchmark measured 0.25 seconds for structuredClone versus 1.3 seconds for the JSON hack when processing one million objects.
The pragmatic assessment: if your object is simple enough that the JSON hack is faster, it is probably simple enough that a shallow spread ({ ...obj }) suffices. If you need a true deep clone, you need structuredClone.
When to Use
Every time you reach for JSON.parse(JSON.stringify()). Every time you consider installing lodash for a single function. Every time you need a true copy of state in a reducer, a cache, a form snapshot, an undo stack, or a worker message.
Lodash cloneDeep: 17 KB, 22 million weekly downloads, ten years without an update. structuredClone: 0 KB, built in, maintained by every browser vendor. One of these is a dependency you chose. The other is a platform feature you already have.
One does note the difference.
Read the full article on vivianvoss.net →
By Vivian Voss — System Architect & Software Developer. Follow me on LinkedIn for daily technical writing.

Top comments (0)