structuredClone(): The Right Way to Deep Copy in JavaScript
The JSON Hack Is Over
For years, the standard way to deep copy an object in JavaScript was this:
const clone = JSON.parse(JSON.stringify(original));It worked for simple objects, but it was a hack with real consequences: it silently drops undefined values, converts Date objects to strings, can’t handle Map, Set, RegExp, ArrayBuffer, or circular references, and throws on BigInt. If you’ve ever had a cloned object behave differently than the original and spent an hour debugging, the JSON roundtrip was probably the culprit.
structuredClone() is the native replacement. It’s built into every modern browser and Node.js, handles all the types JSON can’t, and doesn’t silently corrupt your data.
Basic Usage
The API is about as simple as it gets:
const original = {
name: 'Alice',
joined: new Date('2026-01-15'),
roles: new Set(['admin', 'editor']),
preferences: {
theme: 'dark',
notifications: true
}
};
const clone = structuredClone(original);
// The clone is completely independent
clone.preferences.theme = 'light';
clone.roles.add('viewer');
console.log(original.preferences.theme); // 'dark' — unchanged
console.log(original.roles.has('viewer')); // false — unchanged
// Date objects are properly cloned (not stringified)
console.log(clone.joined instanceof Date); // true
console.log(clone.joined.getFullYear()); // 2026Compare that to JSON.parse(JSON.stringify(original)), which would have turned joined into a string, completely lost the Set, and given you a plain array or nothing at all.
What structuredClone Handles
The algorithm used by structuredClone() is the same structured clone algorithm used by postMessage(), IndexedDB, and the History API. It handles a wide range of types:
| Type | structuredClone | JSON roundtrip |
|---|---|---|
| Plain objects / arrays | Yes | Yes |
Date | Yes (preserves type) | Converts to string |
Map / Set | Yes | Lost entirely |
RegExp | Yes | Becomes empty object {} |
ArrayBuffer / TypedArray | Yes | Lost |
Blob / File | Yes | Lost |
BigInt | Yes | Throws TypeError |
| Circular references | Yes | Throws TypeError |
undefined values | Preserved | Silently dropped |
NaN / Infinity | Preserved | Becomes null |
Circular References
One of the most common edge cases: objects that reference themselves. JSON.stringify() throws a TypeError. structuredClone() handles it correctly.
const obj = { name: 'root' };
obj.self = obj; // Circular reference
// JSON.stringify(obj); // Throws: TypeError: Converting circular structure
const cloned = structuredClone(obj);
console.log(cloned.self === cloned); // true — circular ref preserved
console.log(cloned === obj); // false — different objectThis comes up more often than you’d think — tree structures with parent references, linked lists, cached graph traversals. If your data structure has any circularity, structuredClone() is the only native option that works.
What structuredClone Cannot Clone
The structured clone algorithm has intentional limitations. It throws a DataCloneError for:
- Functions — can’t be serialized. If your object has methods, they’ll cause an error.
- DOM nodes —
Element,Document, etc. are not cloneable. - Symbols — Symbol-keyed properties are ignored.
- Property descriptors — getters, setters, and non-enumerable properties are not preserved. Only the data values are copied.
- Prototype chains — the clone is a plain object. Class instances lose their prototype and methods.
class User {
constructor(name) { this.name = name; }
greet() { return `Hi, I'm ${this.name}`; }
}
const user = new User('Alice');
const clone = structuredClone(user);
console.log(clone.name); // 'Alice' — data is copied
console.log(clone.greet); // undefined — method is lost
console.log(clone instanceof User); // falseIf you need to clone class instances with methods, you’ll need a custom clone method on your class or a library like Lodash’s cloneDeep.
The Transferable Trick
The second argument to structuredClone() lets you transfer objects instead of copying them. Transferred objects become unusable in the original context but avoid the cost of copying large binary data.
const buffer = new ArrayBuffer(1024 * 1024); // 1 MB
// Transfer instead of copy — zero-copy for large buffers
const clone = structuredClone(buffer, { transfer: [buffer] });
console.log(clone.byteLength); // 1048576 — the clone has the data
console.log(buffer.byteLength); // 0 — original is neuteredThis is mainly useful when working with large binary data (images, audio, video buffers) where copying would be expensive. In most application code, you won’t need it.
Performance: When to Use What
Here’s my recommendation based on what you’re actually copying:
| Scenario | Best Approach |
|---|---|
| Shallow copy of a flat object | Spread: { ...obj } |
| Shallow copy of an array | [...arr] or arr.slice() |
| Deep copy of JSON-safe data | structuredClone() |
| Deep copy with Dates, Maps, Sets | structuredClone() |
| Deep copy of class instances | Custom clone method or Lodash |
| Large binary data (ArrayBuffer) | structuredClone() with transfer |
For simple, flat objects, the spread operator is orders of magnitude faster than structuredClone(). Don’t use structuredClone() when a shallow copy is sufficient — it’s overkill.
For deeply nested objects, structuredClone() and JSON.parse(JSON.stringify()) perform similarly in benchmarks on JSON-safe data (JSON is sometimes faster for very simple objects). But structuredClone() wins on correctness and type preservation every time. In production, correctness beats micro-optimization.
Real-World Use Cases
State Management Snapshots
// Save a deep copy of app state before a risky operation
const snapshot = structuredClone(appState);
try {
performRiskyMigration(appState);
} catch (error) {
// Rollback to snapshot
Object.assign(appState, snapshot);
}Immutable Updates in Reducers
function reducer(state, action) {
const next = structuredClone(state);
switch (action.type) {
case 'UPDATE_USER':
next.user.name = action.payload.name;
return next;
default:
return state;
}
}Test Fixtures
const baseFixture = {
user: { id: 1, name: 'Test User', created: new Date() },
items: new Map([['a', 1], ['b', 2]])
};
// Each test gets a fresh, independent copy
beforeEach(() => {
testData = structuredClone(baseFixture);
});Common Mistakes
1. Cloning Objects With Functions
structuredClone({ handler: () => {} }) throws a DataCloneError. If your object contains callbacks or methods, strip them before cloning or use a different approach.
2. Expecting Prototype Preservation
Cloned objects are plain Object instances. If you clone a class instance, the clone won’t have the class’s methods or pass instanceof checks.
3. Using structuredClone for Shallow Copies
If your object is flat (no nested objects), { ...obj } is 10-100x faster. structuredClone() traverses the entire object graph — unnecessary overhead for a flat structure.
Best Practices
- Default to
structuredClone()for any deep copy. It’s correct by default and handles edge cases JSON can’t. - Use spread for shallow copies. Flat objects don’t need deep cloning.
- Strip functions before cloning if your object mixes data and callbacks. Clone the data, reattach the functions after.
- Use the transfer option for large
ArrayBufferdata to avoid expensive copies. - Remove Lodash’s
cloneDeepfrom your bundle if you’re only using it for deep copies of plain data.structuredClone()is a native, zero-dependency replacement.
Wrapping Up
structuredClone() is one of those APIs that should have existed from day one. It deep copies objects correctly, handles all the types that JSON.parse(JSON.stringify()) breaks on, supports circular references, and is available everywhere — browsers, Node.js, Deno, Bun, workers.
The JSON hack had a good run. Time to retire it.
FAQ
Is structuredClone available in Node.js?
Yes, since Node.js 17. It’s a global function — no import needed. It’s also available in Deno, Bun, and all modern browsers.
Can I polyfill structuredClone?
For older environments, the core-js library includes a polyfill. But in 2026, all actively supported platforms have native support. You’re unlikely to need a polyfill unless you’re supporting very old Node.js versions.
Does structuredClone copy Symbol-keyed properties?
No. Symbol-keyed properties are silently ignored during cloning. Only string-keyed, enumerable own properties are copied. This is by design in the structured clone algorithm.
Should I use structuredClone in React state updates?
It depends. For complex nested state with Maps or Sets, yes. For simple objects, prefer the spread pattern ({ ...state, key: newValue }) or Immer, which is optimized for React’s update patterns and doesn’t clone unchanged branches.
editor's pick
latest video
news via inbox
Nulla turp dis cursus. Integer liberos euismod pretium faucibua

