Copying JavaScript objects looks simple until values contain nested arrays, dates, maps, sets, typed arrays, or circular references. A shallow spread copies only the first level, while the old JSON.stringify and JSON.parse pattern changes or rejects several legitimate JavaScript values.

structuredClone provides a standard deep-cloning operation based on the structured clone algorithm used by browser messaging APIs.

Shallow copies keep nested references

A spread expression creates a new outer object:

const original = {
  user: { name: 'Ari' },
  roles: ['reader']
};

const copy = { ...original };
copy.user.name = 'Sam';

console.log(original.user.name); // Sam

The user object and roles array are still shared. That is correct for a shallow copy, but it is not isolation.

Use a deep clone only when you actually need independent nested data. Deep copying large structures has real allocation and traversal cost.

structuredClone preserves many built-in types

For cloneable values, usage is straightforward:

const original = {
  createdAt: new Date(),
  labels: new Set(['stable', 'public']),
  counts: new Map([['ok', 4]]),
  bytes: new Uint8Array([1, 2, 3])
};

const copy = structuredClone(original);

Unlike JSON serialization, the clone retains supported built-in types instead of reducing everything to plain JSON values.

Circular references are also supported:

const node = { name: 'root' };
node.self = node;

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

Know what cannot be cloned

The algorithm is intentionally not a general object serializer. Functions cannot be cloned:

structuredClone({ run() {} }); // throws DataCloneError

Other host objects may have their own support rules. If your data model contains behavior, open resources, framework instances, or environment-specific objects, treat cloning as an explicit design decision rather than a universal escape hatch.

For application state, plain data structures are usually easier to clone, serialize, inspect, and test.

Do not use JSON cloning as a generic replacement

This pattern is familiar:

const copy = JSON.parse(JSON.stringify(value));

It is appropriate only when JSON semantics are exactly what you want. JSON does not preserve values such as Date, Map, Set, or typed arrays as their original types. It also rejects circular references and omits or changes some unsupported values.

If the requirement is “make a JSON-safe representation,” JSON serialization is correct. If the requirement is “deep-clone supported JavaScript data,” structuredClone better expresses the intent.

Transfer large buffers when ownership can move

Some transferable objects can be moved rather than copied. This matters for large binary buffers passed between execution contexts or processing stages.

const data = new Uint8Array(16 * 1024 * 1024);

const moved = structuredClone(data, {
  transfer: [data.buffer]
});

console.log(data.byteLength);  // 0
console.log(moved.byteLength); // original size

After transfer, the original buffer is detached. That prevents accidental concurrent ownership and avoids copying the underlying bytes.

Transfer only when the original value is intentionally being handed off. If callers still need the source buffer, use ordinary cloning instead.

Cloning does not preserve application identity

A deep clone creates new nested objects. Reference comparisons therefore change:

const original = { settings: { theme: 'dark' } };
const copy = structuredClone(original);

console.log(copy === original); // false
console.log(copy.settings === original.settings); // false

This can matter in UI frameworks, memoization systems, caches, and state stores where object identity is used to detect changes.

Do not clone an entire state tree simply to update one field. Structural sharing or targeted immutable updates are often more efficient and preserve useful identity for unchanged branches.

Treat cloning as a boundary operation

Deep cloning is most useful at clear boundaries:

  • capturing a snapshot before later mutation;
  • isolating data passed to a worker;
  • duplicating a plain-data template;
  • copying test fixtures that individual tests may mutate;
  • moving binary ownership with transferables.

It is less useful as a routine defensive operation between every function. Excessive cloning can hide unclear ownership and create avoidable memory pressure.

Common pitfalls

Assuming class behavior is copied

A clone is about data, not preserving arbitrary executable behavior. If class invariants matter, expose explicit serialization and reconstruction methods.

Cloning because mutation rules are unclear

Define who owns a value and whether mutation is allowed. Copying everything is a costly substitute for an ownership model.

Transferring a buffer that is still needed

Transferred buffers are detached from the source. Treat transfer as an ownership move.

Using a deep clone for equality

Cloning produces equivalent data, not equal object references. Use an equality strategy that matches the data model.

Choose the operation that matches the intent

Use object spread or Object.assign for shallow copies. Use JSON serialization for JSON representations. Use structuredClone for supported deep-copy semantics, and use transferables when large transferable data should change ownership.

The important decision is not which cloning trick is shortest. It is whether the program needs a shallow copy, a serialized representation, an independent deep copy, or an ownership transfer. Making that choice explicit prevents many subtle state and performance bugs.