Understanding StructuredClone: The Native Way to Deep Copy In JavaScript
Updated on · 4 min read|
JavaScript developers struggle with reference types. Unintentional object mutations cause countless debugging hours - a persistent pain point in JavaScript development.
Creating deep copies of objects in JavaScript used to be unnecessarily difficult. While Object.assign()
or the spread operator handle shallow copying well, deep copying nested structures required hacky workarounds or external libraries. The introduction of structuredClone()
solved this problem elegantly.
This article explains how structuredClone()
works, compares it to other copying techniques, and highlights important limitations you should know before using it.
Deep copying in JavaScript: the problem and traditional solutions
JavaScript treats objects as references. When you assign an object to a variable, you create a reference to the original object, not a copy. Changes to one reference affect all references pointing to the same object:
javascriptconst original = { name: "John", profile: { age: 30 } }; const copy = original; // Creates a reference, not a copy copy.profile.age = 31; console.log(original.profile.age); // Outputs: 31 - original was modified!
javascriptconst original = { name: "John", profile: { age: 30 } }; const copy = original; // Creates a reference, not a copy copy.profile.age = 31; console.log(original.profile.age); // Outputs: 31 - original was modified!
This behavior confuses even experienced developers. A common scenario requires modifying a copy of state without affecting the original - critical in Redux or when working with immutable data patterns.
Before structuredClone()
, developers relied on these imperfect solutions:
The JSON roundtrip method
The most common approach used JSON serialization:
javascriptconst original = { name: "John", profile: { age: 30 } }; const copy = JSON.parse(JSON.stringify(original)); copy.profile.age = 31; console.log(original.profile.age); // Still 30 - success!
javascriptconst original = { name: "John", profile: { age: 30 } }; const copy = JSON.parse(JSON.stringify(original)); copy.profile.age = 31; console.log(original.profile.age); // Still 30 - success!
This works for simple objects but fails with:
- Circular references (crashes with a circular structure error)
- Functions (they disappear)
undefined
values (they disappear)- Maps and Sets (convert to empty objects)
- Date objects (convert to strings)
Production bugs occur when dates in cloned objects behave like strings, making this approach risky for general use.
Custom recursive cloning and external libraries
A more robust approach involves writing a custom recursive clone function:
javascriptfunction deepClone(obj) { if (obj === null || typeof obj !== "object") return obj; const copy = Array.isArray(obj) ? [] : {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { copy[key] = deepClone(obj[key]); } } return copy; }
javascriptfunction deepClone(obj) { if (obj === null || typeof obj !== "object") return obj; const copy = Array.isArray(obj) ? [] : {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { copy[key] = deepClone(obj[key]); } } return copy; }
This handles more cases but requires maintaining custom code and still fails with circular references, built-in objects (Date, Map, Set), property descriptors, and Symbol keys.
Most projects eventually use libraries. Lodash's cloneDeep()
became the standard:
javascriptimport { cloneDeep } from "lodash"; const copy = cloneDeep(original);
javascriptimport { cloneDeep } from "lodash"; const copy = cloneDeep(original);
While effective, this requires adding an external dependency.

structuredClone(): benefits and capabilities
The implementation of structuredClone()
in browsers significantly improved deep copying. It's part of the HTML standard and available in all modern browsers and Node.js.
The API is remarkably simple:
javascriptconst original = { name: "John", profile: { age: 30 }, dates: [new Date()], map: new Map([["key", "value"]]), }; // Create a true deep copy const copy = structuredClone(original); copy.profile.age = 31; console.log(original.profile.age); // Still 30
javascriptconst original = { name: "John", profile: { age: 30 }, dates: [new Date()], map: new Map([["key", "value"]]), }; // Create a true deep copy const copy = structuredClone(original); copy.profile.age = 31; console.log(original.profile.age); // Still 30
Unlike JSON methods, structuredClone()
properly handles:
- Circular references
- Date objects (preserves them as actual dates)
- Maps, Sets, RegExp objects, and ArrayBuffers
- Arrays and typed arrays (maintains proper types)
Here's an example with circular references that would crash using JSON:
javascriptconst original = { name: "Circular Object" }; original.self = original; const copy = structuredClone(original); console.log(copy.self === copy); // true - circular reference preserved
javascriptconst original = { name: "Circular Object" }; original.self = original; const copy = structuredClone(original); console.log(copy.self === copy); // true - circular reference preserved
Being built into the language means no external dependencies, better performance in many cases, and consistent behavior across environments. structuredClone()
often outperforms JSON methods for complex objects, especially those with circular references.
Limitations and practical usage
Despite its advantages, structuredClone()
has several important limitations:
Function and DOM node limitations
The biggest limitation is with functions - they cannot be cloned:
javascriptconst original = { name: "John", sayHello: function () { console.log("Hello!"); }, }; try { const copy = structuredClone(original); } catch (error) { console.error(error); // DataCloneError: Function object could not be cloned. }
javascriptconst original = { name: "John", sayHello: function () { console.log("Hello!"); }, }; try { const copy = structuredClone(original); } catch (error) { console.error(error); // DataCloneError: Function object could not be cloned. }
Similarly, live DOM nodes cannot be cloned:
javascriptconst element = document.createElement("div"); try { const copy = structuredClone(element); } catch (error) { console.error(error); // DataCloneError: The object could not be cloned. }
javascriptconst element = document.createElement("div"); try { const copy = structuredClone(element); } catch (error) { console.error(error); // DataCloneError: The object could not be cloned. }
Class instances and property descriptors
When cloning class instances, you get plain objects without their methods:
javascriptclass Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; } } const original = new Person("John"); const copy = structuredClone(original); console.log(copy.name); // "John" console.log(copy instanceof Person); // false console.log(copy.greet); // undefined
javascriptclass Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; } } const original = new Person("John"); const copy = structuredClone(original); console.log(copy.name); // "John" console.log(copy instanceof Person); // false console.log(copy.greet); // undefined
Property attributes like getters, setters, and other property descriptors don't survive in the clone. Error objects can be cloned, but the stack property might be lost depending on the JavaScript environment.

When to use structuredClone() and browser support
Despite these limitations, structuredClone()
excels when:
- You need to preserve complex data structures like nested application state
- Your objects might have circular references
- You need to create snapshots of API response data for manipulation
- You're implementing undo/redo functionality requiring state history
For best results:
- Separate data from behavior, keeping pure data objects (which clone well) separate from objects with methods
- Add error handling with try/catch blocks in critical code paths
- Consider performance needs (JSON methods might still be faster for simple objects)
- Implement custom clone methods for class instances
structuredClone()
is available in:
- Chrome 98+, Firefox 94+, Safari 15.4+, Edge 98+
- Node.js 17.0.0+ (as a global function)
- Node.js 16.17.0+ (LTS) as a global function
- Node.js 15.0.0 - 16.16.0 via
util.structuredClone()
For older environments, use polyfills or traditional methods.
Conclusion
The addition of structuredClone()
to JavaScript significantly improved developer experience. While it can't handle functions or preserve prototype chains, it elegantly solves most deep copying needs without external dependencies.
The next time you reach for JSON.parse/stringify or add Lodash just for deep cloning, consider whether structuredClone()
might work better. For data-heavy applications that manipulate copies of complex objects, it has become the preferred choice for many developers.
References and resources
- MDN: structuredClone()
- HTML Living Standard: structuredClone
- MDN: The structured clone algorithm
- Can I Use: structuredClone
- MDN: Object.assign()
- MDN: JSON.stringify()
- MDN: JSON.parse()
- Lodash Documentation: cloneDeep
- The Most Common Mistakes When Using React: Pass by value vs. pass by reference
- Immutable Array Operations in JavaScript: Introducing toSorted, toSpliced, and toReversed
- Simplifying Code with Maps in JavaScript
- Beyond console.log: Debugging techniques in JavaScript
- JavaScript Higher-Order Functions Explained