Question

Deep Cloning Objects in JavaScript: Methods, Trade-offs, and Best Practices

javascriptobjectclone

Question

How can a JavaScript object be deep cloned efficiently and reliably?

I have seen code such as:

const copy = eval(uneval(original));

However, that approach is non-standard and was only supported in Firefox.

I have also used:

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

but I am unsure about its efficiency and limitations.

I have come across recursive copy functions as well, but many of them seem to have edge-case problems.

What is the most practical and efficient way to deep clone an object in JavaScript, and what trade-offs should be considered?

Short Answer

By the end of this page, you will understand what deep cloning means in JavaScript, why it is different from simple assignment or shallow copying, and which cloning techniques are appropriate in modern code. You will also learn the strengths and limitations of structuredClone(), JSON.parse(JSON.stringify(...)), and manual recursive cloning.

Concept

Deep cloning means creating a completely independent copy of a value, including all nested objects and arrays.

In JavaScript, objects are assigned and passed by reference. That means this code does not create a new independent object:

const original = { user: { name: "Ava" } };
const copy = original;

copy.user.name = "Leo";
console.log(original.user.name); // "Leo"

Both variables point to the same object in memory.

A shallow copy creates a new top-level object, but nested values are still shared:

const original = { user: { name: "Ava" } };
const copy = { ...original };

copy.user.name = "Leo";
console.log(original.user.name); // "Leo"

A deep clone copies nested structures too:

const original = { user: { name: "Ava" } };
const copy = structuredClone(original);

copy.user.name = "Leo";
console.log(original.user.name); // "Ava"

This matters in real programming because shared references can cause accidental bugs. For example:

  • Updating form state may unexpectedly mutate original data.
  • Modifying API response objects may affect cached values.
  • Reusing configuration objects across modules can lead to hidden side effects.

There is no single perfect clone technique for every value type. The right choice depends on what the object contains:

  • Plain JSON-like data: structuredClone() or JSON methods may work.
  • Dates, Maps, Sets, typed arrays: structuredClone() is usually better.
  • Functions, class instances, DOM nodes, special prototypes: cloning may need custom logic or should be avoided.

In modern JavaScript, structuredClone() is the standard built-in choice for many deep cloning needs.

Mental Model

Think of an object as a folder that contains files and subfolders.

  • Assignment is like writing down a second shortcut to the same folder.
  • Shallow copy is like making a new top-level folder but keeping shortcuts to the same subfolders inside.
  • Deep clone is like copying the entire folder tree so every subfolder and file is duplicated.

If you change a file in a true deep clone, the original folder stays unchanged.

That is why deep cloning is useful when you need a safe, isolated copy of nested data.

Take Quiz

Syntax and Examples

Core syntax

Modern built-in approach: structuredClone()

const clone = structuredClone(original);

This is the preferred modern solution for many data structures.

JSON-based approach

const clone = JSON.parse(JSON.stringify(original));

This works only for JSON-safe data.

Example: nested object

const original = {
  name: "Laptop",
  specs: {
    ram: 16,
    storage: 512
  }
};

const clone = structuredClone(original);
clone.specs.ram = 32;

console.log(original.specs.ram); // 16
console.log(clone.specs.ram);    // 32

Why this works

  • structuredClone() creates a new object.
  • It also creates new nested objects.
  • Changing clone.specs.ram does not affect original.specs.ram.

Example: where JSON cloning loses information

const original = {
  createdAt: new Date("2024-01-01"),
  missingValue: undefined,
  calculate() {
    return 42;
  }
};

const clone = JSON.parse(JSON.stringify(original));

console.log(clone);
// {
//   createdAt: "2024-01-01T00:00:00.000Z"
// }

What was lost

  • Date became a string.
  • undefined was removed.
  • The function was removed.

Example: arrays are also deeply cloned

const original = {
  tags: ["js", "clone", "data"]
};

const clone = structuredClone(original);
clone.tags.push("tutorial");

console.log(original.tags); // ["js", "clone", "data"]
console.log(clone.tags);    // ["js", "clone", "data", "tutorial"]

Step by Step Execution

Traceable example

const original = {
  profile: {
    name: "Mia",
    skills: ["JavaScript", "CSS"]
  }
};

const clone = structuredClone(original);
clone.profile.name = "Noah";
clone.profile.skills.push("HTML");

console.log(original);
console.log(clone);

Step by step

1. Create original

const original = {
  profile: {
    name: "Mia",
    skills: ["JavaScript", "CSS"]
  }
};

Memory idea:

  • original points to one object.
  • Inside it, profile points to another object.
  • skills points to an array.

2. Deep clone it

const clone = structuredClone(original);

Now:

  • clone points to a different top-level object.
  • clone.profile is a different nested object.
  • clone.profile.skills is a different array.

The values are the same, but the references are different.

3. Change a nested property in the clone

clone.profile.name = "Noah";

Only the cloned nested object changes.

4. Modify the cloned nested array

clone.profile.skills.push("HTML");

Only the cloned array changes.

5. Print both objects

console.log(original);
console.log(clone);

Result:

original = {
  profile: {
    name: "Mia",
    skills: ["JavaScript", "CSS"]
  }
}

clone = {
  profile: {
    name: "Noah",
    skills: ["JavaScript", "CSS", "HTML"]
  }
}

This confirms that nested data was copied independently.

Real World Use Cases

Common practical uses

State updates in applications

When working with nested state, you may need a safe copy before modifying data.

const draftState = structuredClone(appState);
draftState.user.settings.theme = "dark";

Copying API data before transformation

You may want to preserve the original server response.

const rawResponse = await fetchData();
const workingCopy = structuredClone(rawResponse);

Undo/redo features

Editors and drawing tools often store snapshots.

history.push(structuredClone(currentDocument));

Test isolation

In tests, cloning fixture data prevents one test from affecting another.

const input = structuredClone(sampleUserData);

Worker communication

The structured clone algorithm is also used when passing data between threads such as Web Workers.

Take Quiz

Real Codebase Usage

In real projects, developers often avoid deep cloning unless they actually need it, because cloning large objects has a cost.

Common patterns

1. Clone only the part you need

Instead of cloning a whole object tree, copy only the branch being changed.

const updatedUser = {
  ...user,
  settings: {
    ...user.settings,
    theme: "dark"
  }
};

This is often more efficient than deep cloning everything.

2. Use deep cloning at system boundaries

Deep cloning is common when:

  • reading external input
  • creating immutable snapshots
  • sanitizing data before mutation
  • preparing messages for workers

3. Guard against unsupported data

If a value may contain functions or DOM elements, developers often validate before cloning.

function safeClone(value) {
  try {
    return structuredClone(value);
  } catch {
    return null;
  }
}

4. Prefer data modeling over constant cloning

In larger codebases, repeated deep cloning may signal a design issue. Teams often restructure data flow so fewer full copies are needed.

5. Use libraries carefully

Some projects use utility libraries for cloning, but modern JavaScript often makes structuredClone() sufficient for many cases.

Common Mistakes

1. Confusing assignment with copying

const a = { x: 1 };
const b = a;
b.x = 99;

console.log(a.x); // 99

Why it happens

b is just another reference to the same object.

How to avoid it

Use a copy strategy when you need independence.

2. Assuming spread syntax is a deep clone

const a = { nested: { x: 1 } };
const b = { ...a };

b.nested.x = 99;
console.log(a.nested.x); // 99

Why it happens

Spread copies only the top level.

3. Using JSON cloning for unsupported values

const a = {
  date: new Date(),
  value: undefined,
  fn: () => 1
};

const b = JSON.parse(JSON.stringify(a));
console.log(b);

Problem

  • Date becomes a string
  • undefined disappears
  • functions disappear

How to avoid it

Use structuredClone() for supported non-JSON data types.

4. Trying to clone functions

const a = {
  fn() {
    return 1;
  }
};

const b = structuredClone(a); // throws in many environments

Why it happens

Functions are not structured-cloneable.

How to avoid it

Clone data, not behavior. Keep functions outside data objects.

5. Ignoring circular references

const a = {};
a.self = a;

JSON.parse(JSON.stringify(a)); // throws

How to avoid it

structuredClone() can handle circular references, while JSON methods cannot.

6. Cloning class instances and expecting full behavior

Some cloning methods copy data but do not preserve custom class behavior exactly as expected.

How to avoid it

If you need full instance behavior, create a dedicated copy method for that class.

Comparisons

Deep clone method comparison

MethodDeep clone?Handles nested objectsHandles circular referencesPreserves Date/Map/Set betterHandles functionsNotes
= assignmentNoNoYes, but sharedNoYes, same referenceJust copies the reference
Spread / Object.assign()Shallow onlyTop level onlyN/ANoYes, same referenceGood for shallow updates
JSON.parse(JSON.stringify())Yes, for JSON dataYesNoNoNoSimple but lossy
structuredClone()YesYesYesYesNoBest built-in modern option
Manual recursive cloneSometimesYes, if implemented wellMaybeMaybeMaybeEasy to get wrong

structuredClone() vs JSON cloning

FeaturestructuredClone()JSON clone
Standard built-inYesYes
Keeps circular referencesYesNo
Supports DateYesNo, becomes string
Supports Map and SetYesNo
Supports functionsNoNo
Good for plain API JSONYesYes
Good for general object graphsUsuallyLimited

When to choose which

  • Use structuredClone() for modern, general-purpose deep copying of data.
  • Use JSON cloning only for plain JSON-compatible objects when you understand the limitations.
  • Use manual copying when you need custom behavior for specific object types.
Take Quiz

Cheat Sheet

Quick reference

Assignment

const copy = original;
  • Not a copy
  • Same reference

Shallow copy

const copy = { ...original };
const copy2 = Object.assign({}, original);
  • Copies only the top level
  • Nested objects are still shared

Deep clone with modern JavaScript

const copy = structuredClone(original);
  • Best built-in option for many cases
  • Handles nested objects, arrays, Date, Map, Set, and circular references
  • Does not clone functions or some special objects

Deep clone with JSON

const copy = JSON.parse(JSON.stringify(original));
  • Works for plain JSON-safe data
  • Loses undefined, functions, symbols
  • Converts Date to string
  • Fails on circular references

Rule of thumb

  • Need a safe modern deep copy of data? Use structuredClone().
  • Need only a nested update? Prefer selective shallow copies.
  • Have functions or class instances? Consider custom copy logic.

Edge cases

structuredClone({ fn: () => 1 }); // error
const a = {};
a.self = a;
structuredClone(a); // works
JSON.parse(JSON.stringify({ value: undefined })); // {}

FAQ

What is the difference between deep clone and shallow copy in JavaScript?

A shallow copy duplicates only the first level. Nested objects and arrays are still shared. A deep clone creates independent copies of nested values too.

Is structuredClone() the best way to deep clone in JavaScript?

For modern JavaScript and cloneable data, yes. It is the best built-in general-purpose option in most cases.

Why is JSON.parse(JSON.stringify(obj)) not always safe?

Because it only works for JSON-compatible data. It removes undefined and functions, converts Date objects to strings, and fails on circular references.

Can structuredClone() clone functions?

No. Functions are not supported by the structured clone algorithm.

Does spread syntax create a deep clone?

No. Spread syntax creates only a shallow copy.

Is deep cloning always a good idea?

No. It can be slower and use more memory for large objects. Often it is better to copy only the parts you need to change.

Can circular references be deep cloned?

Yes, with structuredClone(). No, with JSON cloning.

Should I use eval(uneval(obj)) for cloning?

No. It is non-standard, unsafe in many contexts, and not recommended for modern JavaScript.

Take Quiz

Mini Project

Description

Build a small utility that creates a working copy of application settings before making changes. This demonstrates why deep cloning matters when objects contain nested configuration values.

Goal

Create a function that safely clones a settings object, updates the clone, and proves that the original object is unchanged.

Requirements

  • Create a nested settings object with at least one nested object and one array.
  • Deep clone the settings object using a modern JavaScript approach.
  • Modify values only in the cloned object.
  • Print both the original and cloned objects to show that they are independent.
Take Quiz

Keep learning

Related questions

View more