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
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.
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,Setand 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
undefinedas 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:
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.
import _ from 'lodash';
const clone = _.cloneDeep(user);Advantages:
- it deep copies any data types (including
Date,Map,Set,Bufferand so on); - it behaves consistently in every environment.
Drawback:
- it requires installing the library:
npm install lodashOption 4: a manual recursive copy (for understanding)
If you want to understand the mechanics, here is a basic implementation:
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
| Method | Support | Copies nested objects | Preserves types (Date, Map, Set) | Copies functions |
|---|---|---|---|---|
structuredClone() | Modern browsers, Node.js 17+ | Yes | Yes | No |
JSON.parse(JSON.stringify()) | Everywhere | Yes | No | No |
_.cloneDeep() | Through the lodash library | Yes | Yes | No |
| Recursive function | Manual implementation | Yes | No | No |
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
Dateis a string, andclone.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 throwsDataCloneErrorinstead 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 readyA concise answer to help you respond confidently on this topic during an interview.