How to Clone Complex JavaScript Objects with structuredClone()

Deep cloning in JavaScript has always been tricky. Developers often relied on hacks like JSON.stringify() + JSON.parse() or external libraries, which only worked for the simplest cases. But now, there’s a native and powerful alternative: structuredClone().

In this guide, you’ll learn how structuredClone() works, what it supports, where it breaks, how it compares to other methods, and where to use it in real-world apps.

Deep cloning in JavaScript

🔄 Why structuredClone() Is Not Just JSON 2.0

Let’s start with a classic problem.

js
123456789
      const data = {
  date: new Date(),
  regex: /hello/gi,
  map: new Map([['key', 'value']]),
};

const jsonCopy = JSON.parse(JSON.stringify(data));
console.log(jsonCopy);
// Output: { date: "2025-02-02T12:00:00.000Z", regex: {}, map: {} }
    

You lose critical types:

  • Date becomes a string
  • RegExp becomes an empty object
  • Map becomes

Worse, if your object has circular references, it’ll crash:

js
1234
      const node = {};
node.self = node;

JSON.stringify(node); // ❌ TypeError: Converting circular structure to JSON
    

Enter structuredClone():

js
12345
      const structuredCopy = structuredClone(data);

console.log(structuredCopy.date instanceof Date); // true
console.log(structuredCopy.regex instanceof RegExp); // true
console.log(structuredCopy.map instanceof Map); // true
    

It handles these cases correctly, deeply, and safely.

✅ What structuredClone() Supports

This method works with a wide range of types:

✅ Primitives:

undefined, null, boolean, number, bigint, string, symbol

✅ Data Structures:

  • Objects and arrays
  • Date, RegExp, Map, Set
  • Typed arrays, ArrayBuffer, SharedArrayBuffer
  • Blob, File, ImageData, MessagePort, FileList
  • Browser-specific objects like MessageChannel, OffscreenCanvas

🔬 Binary Example:

js
12345
      const buffer = new ArrayBuffer(16);
const clone = structuredClone(buffer);

console.log(clone.byteLength); // 16
console.log(clone === buffer); // false
    

Useful for transferring binary data to Web Workers or BroadcastChannel.

🔁 Deep Nesting and Circular Structures

structuredClone() can also handle circular references:

js
12345
      const node = { value: 1 };
node.self = node;

const clone = structuredClone(node);
console.log(clone.self === clone); // true
    

This makes it perfect for serializing graphs, trees, and cyclic structures, where JSON completely fails.

🧱 Limitations of structuredClone()

While powerful, it doesn’t handle everything.

❌ 1. Functions

js
12345
      const withFn = {
  sayHi: () => console.log('hi'),
};

structuredClone(withFn); // ❌ DataCloneError
    

Functions can’t be cloned. Behavior (code) is lost.

❌ 2. DOM Elements

js
12
      const div = document.createElement('div');
structuredClone(div); // ❌ DataCloneError
    

UI elements aren’t cloneable — only data is.

❌ 3. Prototypes and Class Instances

js
1234567891011
      class MyClass {
  constructor(x) {
    this.x = x;
  }
}

const instance = new MyClass(42);
const clone = structuredClone(instance);

console.log(clone instanceof MyClass); // false
console.log(clone.x); // 42
    

Only the plain object structure is cloned — not the prototype chain.

⚖️ Performance: Is It Slow?

Compared to JSON.parse(JSON.stringify(...)), yes — structuredClone() is slower for flat structures.

But it’s much more stable, and actually faster for:

  • Nested or deeply recursive objects
  • Objects with circular references
  • Binary data or typed arrays

If performance is critical (e.g., cloning every animation frame), consider alternatives like Object.assign() (shallow) or lodash/cloneDeep.

🆚 Alternatives and Polyfills

📦 Lodash cloneDeep

js
123456
      import cloneDeep from 'lodash/cloneDeep';

import cloneDeep from 'lodash/cloneDeep';

const obj = { a: 1, b: { c: 2 } };
const deep = cloneDeep(obj);
    

Works for nested objects and arrays — but:

  • Can’t clone Map, Set, Date accurately
  • Won’t handle circular structures unless configured

🌐 Browser Support

  • ✅ Supported: Chrome 98+, Firefox 94+, Safari 15+, Node.js 17+
  • ❌ Not Supported: IE11, Safari < 14, Node < 17

For older environments, use:

  • structured-clone polyfill
  • Fallback to cloneDeep() or JSON.parse() with checks

🚀 Real-World Use Cases

1. Undo/Redo in Rich Editors

When building complex editors (think Notion, Figma, Miro), you need to clone the entire state, including nested structures, maps, and cyclic references.

js
123
      function saveSnapshot(state) {
  historyStack.push(structuredClone(state));
}
    
  • Preserves deep state
  • Avoids accidental mutations
  • Eliminates bug-prone manual deep clones

2. State Sync Between Tabs with BroadcastChannel

js
12
      const channel = new BroadcastChannel('sync');
channel.postMessage(structuredClone(store));
    

On receiving:

js
123
      channel.onmessage = e => {
  syncWithStore(e.data);
};
    

Ensures:

  • Map, Date, Blob stay intact
  • Cycles don’t crash the sync
  • Fast, native serialization

3. Passing Data to Web Workers

js
12
      const worker = new Worker('worker.js');
worker.postMessage(structuredClone(bigData));
    

Structured clone avoids Transferable limitations and keeps data safe.

⚠️ When Not to Use It

❌ ScenarioAlternative
You need methods or class instancesManual clone or class-transformer
Performance-critical, cloning every frameShallow copy or tuned lib
Old browser/Node.js versionPolyfill or fallback
Cloning UI elements or DOM nodesManual serialization

🧠 Final Thoughts

structuredClone() is the modern way to deep clone in JavaScript:

  • Safe
  • Native
  • Handles complex structures and cycles
  • Ideal for serialization, syncing, undo stacks

It’s especially powerful in React, Vue, and Svelte apps for state snapshotting, and is perfect for passing data to IndexedDB, postMessage, or between browser tabs.

Use it with care — know where it shines and where it doesn’t. And say goodbye to JSON.parse(JSON.stringify(...)) once and for all.