Skip to main content

Deep copy of an object

To make a deep copy of an object, use the built-in structuredClone(obj), or _.cloneDeep(obj) from lodash in larger projects. A deep copy is a completely independent object, including every nested object and array, so changes on the copy do not affect the original.

Theory

TL;DR

  • A deep copy rebuilds the whole tree of the object, not just the top level.
  • structuredClone(obj) is built in, modern and fast, but it does not copy functions or prototypes.
  • JSON.parse(JSON.stringify(obj)) works everywhere, but it silently loses many types.
  • _.cloneDeep(obj) from lodash is the most reliable option for complex structures.
  • Your own recursive function is useful for understanding the mechanics, but it does not cover Map, Set, Date, RegExp.
  • Rule of thumb: structuredClone() by default, cloneDeep() when you need maximum compatibility.

Quick example

javascript
const user = { name: 'Alice', address: { city: 'Kyiv', zip: 12345 }, tags: ['dev', 'frontend'] }; const clone = structuredClone(user); clone.address.city = 'Lviv'; clone.tags.push('react'); console.log(user.address.city); // "Kyiv" console.log(user.tags); // ['dev', 'frontend']

What a "deep copy" is

A deep copy is when a fully independent object is created, including all nested objects and arrays. Changes on the copy do not affect the original.

The opposite is a shallow copy ({ ...obj }, Object.assign()), which copies only the top level and leaves nested objects as shared references.

Option 1: the modern way, structuredClone()

The most reliable and simplest method, available in every modern browser and in Node.js 17 and newer.

javascript
const user = { name: 'Alice', address: { city: 'Kyiv', zip: 12345 }, tags: ['dev', 'frontend'] }; const clone = structuredClone(user); clone.address.city = 'Lviv'; clone.tags.push('react'); console.log(user.address.city); // "Kyiv" console.log(user.tags); // ['dev', 'frontend']

Advantages:

  • it copies objects, arrays, dates, Map, Set and other structured types;
  • it is safe and fast, because it is implemented in the engine.

Limitations:

  • it does not copy functions or prototypes (they are lost);
  • it does not support undefined as a key (but it does support it as a value).

Option 2: JSON.parse(JSON.stringify())

The classic approach that works everywhere, even in old browsers:

javascript
const user = { name: 'Alice', address: { city: 'Kyiv' }, tags: ['frontend', 'dev'] }; const clone = JSON.parse(JSON.stringify(user)); clone.address.city = 'Lviv'; console.log(user.address.city); // "Kyiv"

Advantages:

  • simple and universal, with no dependencies.

Drawbacks:

  • it loses:
    • functions;
    • undefined, Symbol, BigInt;
    • Date, Map, Set, RegExp (they turn into plain objects or strings).
  • it is slower on very large objects.

Option 3: lodash (_.cloneDeep())

If the project is large or uses TypeScript, this is the most reliable option.

javascript
import _ from 'lodash'; const clone = _.cloneDeep(user);

Advantages:

  • it deep copies any data types (including Date, Map, Set, Buffer and so on);
  • it behaves consistently in every environment.

Drawback:

  • it requires installing the library:
bash
npm install lodash

Option 4: a manual recursive copy (for understanding)

If you want to understand the mechanics, here is a basic implementation:

javascript
function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { return obj.map(deepClone); } const clone = {}; for (const key in obj) { clone[key] = deepClone(obj[key]); } return clone; } const user = { name: 'Alice', address: { city: 'Kyiv' } }; const clone = deepClone(user); clone.address.city = 'Lviv'; console.log(user.address.city); // "Kyiv"

It works for plain objects and arrays, but not for Map, Set, Date, RegExp and the like: for those you need lodash or structuredClone.

Summary table

MethodSupportCopies nested objectsPreserves types (Date, Map, Set)Copies functions
structuredClone()Modern browsers, Node.js 17+YesYesNo
JSON.parse(JSON.stringify())EverywhereYesNoNo
_.cloneDeep()Through the lodash libraryYesYesNo
Recursive functionManual implementationYesNoNo

In one sentence:

For a reliable deep copy use structuredClone(obj) when you want a built-in function, and _.cloneDeep(obj) when the project is large and stability matters.

Common mistakes

  • Confusing a deep copy with a shallow one. { ...obj } creates a new object, but the nested structures stay shared.
  • Cloning an object with dates through JSON. After that clone a Date is a string, and clone.createdAt.getTime() throws.
  • Expecting methods to survive the clone. Neither structuredClone(), nor the JSON trick, nor a simple recursion carries the prototype over, so a class instance becomes a plain object.
  • Calling structuredClone() on an object containing a function. It throws DataCloneError instead of silently dropping the property the way JSON does.
  • Using naive recursion on cyclic structures. Without a cache of already copied objects (a WeakMap, for instance) it loops forever and overflows the stack.
  • Pulling in lodash for a single call in a modern environment. structuredClone() is already there.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.