Structured Clone Algorithm
The structuredClone()algorithm is JavaScript's built-in way to create deep copies of values. It was standardized in ES2022 and is now supported in all modern browsers and Node.js 17+. Before structuredClone, developers relied on JSON.parse(JSON.stringify()) or manual recursive copying — both of which had significant limitations.
The structured clone algorithm traverses the input value recursively, creating a deep copy of each property. Unlike JSON serialization, it handles circular references, typed arrays, Date, RegExp, Map, Set, Blob, File, ArrayBuffer, and other complex types. It is also faster than manual deep copy implementations in most engines.
| 1 | // Shallow copy — shares nested references |
| 2 | const original = { name: "Alice", scores: [95, 87, 92] }; |
| 3 | const shallow = { ...original }; |
| 4 | |
| 5 | shallow.name = "Bob"; |
| 6 | shallow.scores.push(100); |
| 7 | |
| 8 | console.log(original.name); // "Alice" — unchanged (primitive) |
| 9 | console.log(original.scores); // [95, 87, 92, 100] — mutated! (reference) |
| 10 | |
| 11 | // Deep copy — independent nested objects |
| 12 | const deep = structuredClone(original); |
| 13 | |
| 14 | deep.name = "Charlie"; |
| 15 | deep.scores.push(75); |
| 16 | |
| 17 | console.log(original.name); // "Alice" — unchanged |
| 18 | console.log(original.scores); // [95, 87, 92, 100] — unchanged |
The structuredClone() function takes a value and returns a deep copy. It works with any serializable JavaScript value — primitives, objects, arrays, dates, maps, sets, typed arrays, and more.
| 1 | // Basic deep clone |
| 2 | const user = { |
| 3 | name: "Alice", |
| 4 | age: 30, |
| 5 | address: { |
| 6 | city: "Portland", |
| 7 | state: "OR", |
| 8 | zip: "97201" |
| 9 | }, |
| 10 | hobbies: ["reading", "hiking"] |
| 11 | }; |
| 12 | |
| 13 | const clone = structuredClone(user); |
| 14 | |
| 15 | clone.name = "Bob"; |
| 16 | clone.address.city = "Seattle"; |
| 17 | clone.hobbies.push("coding"); |
| 18 | |
| 19 | console.log(user.name); // "Alice" — unchanged |
| 20 | console.log(user.address.city); // "Portland" — unchanged |
| 21 | console.log(user.hobbies); // ["reading", "hiking"] — unchanged |
| 22 | console.log(clone.name); // "Bob" |
| 23 | console.log(clone.address.city); // "Seattle" |
| 24 | console.log(clone.hobbies); // ["reading", "hiking", "coding"] |
| 25 | |
| 26 | // Works with primitives |
| 27 | console.log(structuredClone(42)); // 42 |
| 28 | console.log(structuredClone("hello")); // "hello" |
| 29 | console.log(structuredClone(null)); // null |
| 30 | console.log(structuredClone(undefined)); // undefined |
info
The structured clone algorithm handles many built-in JavaScript types that JSON serialization cannot. This makes it suitable for cloning complex data structures that include dates, regular expressions, maps, sets, typed arrays, and more.
| 1 | // Date objects — cloned correctly |
| 2 | const original = new Date("2024-01-15"); |
| 3 | const cloned = structuredClone(original); |
| 4 | console.log(cloned instanceof Date); // true |
| 5 | console.log(cloned.getTime() === original.getTime()); // true |
| 6 | |
| 7 | // RegExp objects — cloned correctly |
| 8 | const pattern = /hello/gi; |
| 9 | const clonedPattern = structuredClone(pattern); |
| 10 | console.log(clonedPattern instanceof RegExp); // true |
| 11 | console.log(clonedPattern.source); // "hello" |
| 12 | console.log(clonedPattern.flags); // "gi" |
| 13 | |
| 14 | // Map objects — cloned correctly |
| 15 | const map = new Map([["key1", "value1"], ["key2", "value2"]]); |
| 16 | const clonedMap = structuredClone(map); |
| 17 | console.log(clonedMap instanceof Map); // true |
| 18 | console.log(clonedMap.get("key1")); // "value1" |
| 19 | console.log(clonedMap !== map); // true — different reference |
| 20 | |
| 21 | // Set objects — cloned correctly |
| 22 | const set = new Set([1, 2, 3, 4, 5]); |
| 23 | const clonedSet = structuredClone(set); |
| 24 | console.log(clonedSet instanceof Set); // true |
| 25 | console.log(clonedSet.size); // 5 |
| 26 | console.log(clonedSet !== set); // true — different reference |
| 27 | |
| 28 | // Typed arrays — cloned correctly |
| 29 | const arr = new Int32Array([1, 2, 3, 4]); |
| 30 | const clonedArr = structuredClone(arr); |
| 31 | console.log(clonedArr instanceof Int32Array); // true |
| 32 | console.log(clonedArr.buffer !== arr.buffer); // true — different ArrayBuffer |
| 1 | // ArrayBuffer and DataView |
| 2 | const buffer = new ArrayBuffer(16); |
| 3 | const view = new DataView(buffer); |
| 4 | view.setUint8(0, 42); |
| 5 | view.setFloat32(4, 3.14); |
| 6 | |
| 7 | const clonedBuffer = structuredClone(buffer); |
| 8 | const clonedView = new DataView(clonedBuffer); |
| 9 | |
| 10 | console.log(clonedView.getUint8(0)); // 42 |
| 11 | console.log(clonedView.getFloat32(4)); // 3.14 |
| 12 | console.log(clonedBuffer !== buffer); // true — different ArrayBuffer |
| 13 | |
| 14 | // Blob and File (browser) |
| 15 | const blob = new Blob(["Hello"], { type: "text/plain" }); |
| 16 | const clonedBlob = structuredClone(blob); |
| 17 | console.log(clonedBlob instanceof Blob); // true |
| 18 | |
| 19 | // Circular references — handled automatically |
| 20 | const circular = { name: "root" }; |
| 21 | circular.self = circular; // circular reference |
| 22 | |
| 23 | const clonedCircular = structuredClone(circular); |
| 24 | console.log(clonedCircular.self === clonedCircular); // true — preserved |
| 25 | console.log(clonedCircular !== circular); // true — different object |
Despite its power, structuredClone has specific limitations. Some JavaScript types cannot be cloned because they contain non-serializable state like functions, DOM nodes, class instances with private fields, or WeakMap/WeakSet references.
| 1 | // These will throw DataCloneError: |
| 2 | // 1. Functions |
| 3 | try { |
| 4 | structuredClone(() => {}); |
| 5 | } catch (e) { |
| 6 | console.error(e.message); // "Failed to execute 'structuredClone'" |
| 7 | } |
| 8 | |
| 9 | // 2. DOM nodes |
| 10 | try { |
| 11 | structuredClone(document.body); |
| 12 | } catch (e) { |
| 13 | console.error(e.message); // "Failed to execute 'structuredClone'" |
| 14 | } |
| 15 | |
| 16 | // 3. WeakMap and WeakSet (weak references can't be cloned) |
| 17 | try { |
| 18 | structuredClone(new WeakMap()); |
| 19 | } catch (e) { |
| 20 | console.error(e.message); |
| 21 | } |
| 22 | |
| 23 | // 4. Symbols (unique identity can't be cloned) |
| 24 | try { |
| 25 | structuredClone({ key: Symbol("test") }); |
| 26 | } catch (e) { |
| 27 | console.error(e.message); |
| 28 | } |
| 29 | |
| 30 | // 5. Class instances with methods (methods are not cloned) |
| 31 | class Animal { |
| 32 | constructor(name) { this.name = name; } |
| 33 | speak() { return `${this.name} makes a noise`; } |
| 34 | } |
| 35 | |
| 36 | const lion = new Animal("Leo"); |
| 37 | const clonedLion = structuredClone(lion); |
| 38 | |
| 39 | console.log(clonedLion.name); // "Leo" — data cloned |
| 40 | console.log(clonedLion.speak); // undefined — methods lost! |
| 41 | console.log(clonedLion instanceof Animal); // false — wrong prototype |
warning
Before structuredClone, developers used several approaches for deep copying. Each has trade-offs. Here is a comparison of the most common methods.
| 1 | // Method 1: JSON.parse(JSON.stringify()) |
| 2 | // Limitations: no Date, no RegExp, no Map/Set, no circular refs, no undefined |
| 3 | const jsonClone = JSON.parse(JSON.stringify(original)); |
| 4 | |
| 5 | // Method 2: Recursive deep clone |
| 6 | function deepClone(obj, seen = new WeakMap()) { |
| 7 | if (obj === null || typeof obj !== "object") return obj; |
| 8 | if (seen.has(obj)) return seen.get(obj); |
| 9 | |
| 10 | if (obj instanceof Date) return new Date(obj); |
| 11 | if (obj instanceof RegExp) return new RegExp(obj); |
| 12 | if (obj instanceof Map) { |
| 13 | const map = new Map(); |
| 14 | seen.set(obj, map); |
| 15 | obj.forEach((v, k) => map.set(deepClone(k, seen), deepClone(v, seen))); |
| 16 | return map; |
| 17 | } |
| 18 | if (obj instanceof Set) { |
| 19 | const set = new Set(); |
| 20 | seen.set(obj, set); |
| 21 | obj.forEach(v => set.add(deepClone(v, seen))); |
| 22 | return set; |
| 23 | } |
| 24 | |
| 25 | const clone = Array.isArray(obj) ? [] : {}; |
| 26 | seen.set(obj, clone); |
| 27 | for (const key of Reflect.ownKeys(obj)) { |
| 28 | clone[key] = deepClone(obj[key], seen); |
| 29 | } |
| 30 | return clone; |
| 31 | } |
| 32 | |
| 33 | // Method 3: structuredClone (recommended) |
| 34 | const clone = structuredClone(original); |
| 35 | |
| 36 | // Comparison: |
| 37 | // JSON.parse/stringify: fast but loses types, no circular refs |
| 38 | // recursive deepClone: flexible but slower, custom code to maintain |
| 39 | // structuredClone: fast, correct, built-in, handles most cases |
best practice