JavaScript structuredClone and Deep Copying

· 5 min read · Updated March 23, 2026 · intermediate
javascript deep-copy structured-clone

Understanding structuredClone and Deep Copying in JavaScript

Making a copy of an object sounds simple until you realise that a shallow copy shares references to nested objects. Mutate the original after copying, and your copy changes too. This is one of the most common sources of unexpected behaviour in JavaScript. structuredClone is the built-in solution — it performs a deep copy of any structured-cloneable value in a single function call.

What is deep copying?

A shallow copy copies only the top-level properties of an object. Nested objects, arrays, and other reference types still point to the original.

const original = { items: [1, 2, 3] };
const shallow = Object.assign({}, original);
shallow.items.push(4);

console.log(original.items); // [1, 2, 3, 4] — the original was mutated!

A deep copy duplicates every nested value recursively, so changes to the clone never affect the original.

Introducing structuredClone

structuredClone is a global function added to JavaScript via the WHATWG structured clone algorithm. It landed in browsers around March 2022 and is also available in Node.js 17+, Deno, and Bun.

const clone = structuredClone(value);
const clone = structuredClone(value, { transfer: [] });

Parameters:

  • value — any structured-cloneable JavaScript value.
  • options.transfer (optional) — an array of transferable objects (ArrayBuffer, MessagePort, ImageBitmap, etc.) that are moved from the source to the destination. The original becomes detached.

Returns: a deep clone of the value. Throws DataCloneError if a value cannot be cloned.

Basic usage

const original = {
  name: 'Alice',
  scores: [98, 85, 91],
  metadata: { role: 'admin', active: true }
};

const clone = structuredClone(original);
clone.scores.push(77);
clone.metadata.role = 'guest';

console.log(original.scores);        // [98, 85, 91] — unchanged
console.log(original.metadata.role);  // 'admin' — unchanged
console.log(clone.scores);            // [98, 85, 91, 77]
console.log(clone.metadata.role);     // 'guest'

The clone is completely independent from the original. No shared references.

Types structuredClone handles well

The structured clone algorithm preserves the type of most common JavaScript values:

TypeNotes
Primitivesstring, number, bigint, boolean, null, undefined
Object, ArrayDeep cloned
DateCloned as Date
RegExpCloned as RegExp
Map / SetKeys/values or elements deep-cloned
TypedArray, ArrayBufferCloned
ErrorCloned as Error (name, message, cause preserved)
Blob, File, FileListCloned
BigIntFully supported (unlike JSON methods)

Example with complex types:

const complex = {
  created: new Date('2024-01-01'),
  data: new Map([['key', 'value']]),
  ratio: Infinity,
  id: BigInt(9007199254740991),
  pattern: /test/gi,
  active: undefined,
  symbol: Symbol('private')
};

const clone = structuredClone(complex);

console.log(clone.created instanceof Date); // true
console.log(clone.data instanceof Map);     // true
console.log(clone.id);                      // 9007199254740991n
console.log(clone.pattern);                 // /test/gi
console.log(clone.active);                 // undefined
console.log(clone.symbol);                 // Symbol(private)

Note that Symbol values as property values are cloned correctly — only Symbol keys are dropped (see the Symbol property keys section below).

Transferring instead of copying

The transfer option moves ownership of large binary buffers rather than copying them. This is useful when passing data between threads or actors and you no longer need the original.

const originalBuffer = new ArrayBuffer(16);
const view = new Uint8Array(originalBuffer);
view[0] = 42;

const clonedBuffer = structuredClone(originalBuffer, {
  transfer: [originalBuffer]
});

console.log(originalBuffer.byteLength);        // 0 — detached!
console.log(clonedBuffer.byteLength);          // 16
console.log(new Uint8Array(clonedBuffer)[0]);  // 42

After the transfer, originalBuffer.byteLength is 0. Only ArrayBuffer can be transferred — SharedArrayBuffer cannot.

Common pitfalls

Even though structuredClone handles most types, there are important limits you need to be aware of.

Circular references throw

Unlike some library functions, structuredClone does not handle circular references. It throws DataCloneError.

const obj = { name: 'test' };
obj.self = obj;

structuredClone(obj);
// DataCloneError: cyc

If your data contains circular references, you need a custom implementation or a library.

Class instances become plain objects

class Person {
  constructor(name) { this.name = name; }
}

const alice = new Person('Alice');
const clone = structuredClone(alice);

console.log(clone instanceof Person);              // false
console.log(Object.getPrototypeOf(clone));          // Object.prototype

The properties are copied, but the prototype chain is lost. The clone is a plain object.

Methods are not clonable

Functions cannot be cloned — they throw DataCloneError. This means any method attached to an object is lost after cloning.

const obj = {
  name: 'Test',
  greet() { return `Hello, ${this.name}`; }
};

const clone = structuredClone(obj);
console.log(clone.greet); // undefined

Property descriptors are lost

Getters, setters, and property metadata (writable, enumerable, configurable) are not preserved. All cloned properties become simple data properties.

const obj = {};
Object.defineProperty(obj, 'secret', {
  get() { return 'hidden'; },
  enumerable: false
});

const clone = structuredClone(obj);
console.log(Object.getOwnPropertyDescriptor(clone, 'secret'));
// { value: 'hidden', writable: true, enumerable: true, configurable: true }

Symbol property keys are dropped

Symbols used as object property keys are currently not preserved by the structured clone algorithm.

const key = Symbol('id');
const obj = { [key]: 42, name: 'test' };

const clone = structuredClone(obj);
console.log(Object.getOwnPropertySymbols(clone)); // [] — Symbol key is gone

structuredClone vs JSON methods

Before structuredClone, the common workaround was JSON.parse(JSON.stringify(value)). It works for simple data, but it has significant limitations:

LimitationJSON.parse(JSON.stringify())structuredClone
FunctionsSilently droppedThrows DataCloneError
undefinedSilently droppedPreserved
BigIntThrows TypeErrorFully supported
DateBecomes a stringPreserved as Date
Map / SetBecomes {}Preserved with correct type
RegExpBecomes {} or lostPreserved as RegExp
Circular referencesThrowsThrows (same, but clearer error)
const original = {
  date: new Date('2024-01-01'),
  set: new Set([1, 2, 2]),
  big: BigInt(9007199254740991)
};

const jsonClone = JSON.parse(JSON.stringify(original));
console.log(typeof jsonClone.date); // 'string' — type is lost
console.log(jsonClone.big);          // undefined — BigInt throws earlier

const properClone = structuredClone(original);
console.log(properClone.date instanceof Date); // true
console.log(properClone.big);                  // 9007199254740991n

For any data that includes Date, BigInt, Map, Set, or RegExp, structuredClone is the clear choice.

Browser and runtime support

structuredClone has broad support:

  • Chrome / Edge: 98+
  • Safari: 15.4+
  • Firefox: 94+
  • Node.js: 17.0.0+
  • Deno: 1.21+

IE11 is not supported. If you need to support legacy environments, fall back to JSON.parse(JSON.stringify()) for simple data or use a library like Lodash’s cloneDeep.

When to use structuredClone

Reach for structuredClone when you need to:

  • Pass complex data to a Web Worker or between windows
  • Store an immutable snapshot of state without shared references
  • Duplicate data before making transformations that should not affect the source
  • Clone plain data objects that include Date, Map, Set, RegExp, or BigInt

For class instances, circular references, or objects with methods, you still need a custom deep copy function or a library.

See Also