JavaScript

Deep clone an object in JavaScript

Make a fully independent copy of a nested object so mutating the copy never touches the original.

JavaScript
function deepClone(value) {
  if (typeof structuredClone === "function") {
    return structuredClone(value);
  }
  // Fallback for older runtimes (loses Dates, Maps, functions).
  return JSON.parse(JSON.stringify(value));
}

const original = { user: { name: "Ada", roles: ["admin"] } };
const copy = deepClone(original);
copy.user.roles.push("editor");

console.log(original.user.roles); // ["admin"] — untouched
console.log(copy.user.roles);     // ["admin", "editor"]
Output
[ 'admin' ]
[ 'admin', 'editor' ]

Open this snippet in the editor

Launches a fresh code space with this JavaScript already loaded — edit it, share the link, or keep building.

Two people on the same code space, live — and JavaScript runs right in the browser, with output in the console.
The ShareCode editor — write or paste your code, then share the link to bring someone in.

How it works

A spread (`{...obj}`) or `Object.assign` only copies the top level, so nested objects and arrays stay shared between the original and the copy. Mutating `copy.user.roles` would then also change `original.user.roles` — a frequent and genuinely hard-to-spot source of bugs, especially in state management where "why did the previous state change?" can cost an afternoon.

`structuredClone`, available in modern browsers and Node 17+, walks the entire object graph and produces a genuinely independent value. It's the right default in 2026 because it also handles things JSON can't: `Date`, `Map`, `Set`, `ArrayBuffer`, typed arrays, and even circular references. One call, correct for the overwhelming majority of plain-data cases.

The `JSON.parse(JSON.stringify(...))` fallback is the old standby and works for simple data, but it silently drops or mangles anything JSON can't represent. `Date` objects come back as strings, `undefined` and functions vanish, `Map` and `Set` turn into empty objects, and a circular reference throws outright. Keep it only as a fallback for ancient runtimes, and know its blind spots.

Neither built-in approach preserves class identity. A cloned instance of your own class becomes a plain object — its prototype, methods, and any private fields are gone. When you need real instances back, write a custom clone (below) or give the class its own `clone()` method that reconstructs itself.

Variations

structuredClone with circular references

Where the JSON trick throws, structuredClone just works — including objects that reference themselves.

JavaScript
const node = { name: "root" };
node.self = node;            // circular

const copy = structuredClone(node);
console.log(copy.self === copy);   // true — cycle preserved
console.log(copy === node);        // false — independent copy

Custom recursive clone

When you need full control (or to preserve class instances), a small recursive clone handles arrays and plain objects and you extend it for the types you care about.

JavaScript
function clone(value) {
  if (value === null || typeof value !== "object") return value;
  if (Array.isArray(value)) return value.map(clone);
  if (value instanceof Date) return new Date(value);
  return Object.fromEntries(
    Object.entries(value).map(([k, v]) => [k, clone(v)]),
  );
}

Snapshotting state before a mutation

Immutable update patterns (Redux, undo/redo stacks, optimistic UI) rely on never mutating the previous state. Deep-clone the slice you're about to change so the old reference stays a faithful snapshot you can roll back to.

JavaScript
function applyEdit(state, edit) {
  const next = structuredClone(state);
  next.document.title = edit.title;   // mutate the copy freely
  return next;                        // old state is untouched
}

Common mistakes & good to know

  • structuredClone throws on values it can't clone — functions, DOM nodes, class instances with private fields. Clone plain data, not live objects.
  • Neither structuredClone nor the JSON trick restores a prototype chain. A cloned class instance becomes a plain object; write a custom clone if you need real instances.
  • Deep cloning large structures isn't free. If you only change one branch, copy along that path (spread the affected levels) instead of cloning the whole tree.
  • The JSON fallback turns Dates into strings and drops undefined/functions/Maps. Don't use it when the data contains those.

Frequently asked questions

Why isn't the spread operator enough?

Spread and Object.assign are shallow — they copy the top level but keep references to nested objects and arrays. Mutating a nested value on the copy also mutates the original.

Is structuredClone widely supported?

Yes — all current browsers and Node 17+. It's the recommended default. Keep a JSON fallback only if you must support very old runtimes.

Does it clone functions or class methods?

No. structuredClone throws on functions and doesn't preserve class prototypes. For those, write a custom clone or a clone() method on the class.

Is deep cloning a performance concern?

For small objects, no. For large or deeply nested structures cloned on a hot path, yes — prefer structural sharing, copying only the branch you change.

Related snippets

Previous

Debounce a function in JavaScript

Next

Group a list of dictionaries by key in Python