How to Clone a JavaScript Object without Errors

Learn safe ways to clone JavaScript objects: shallow copy with spread and Object.assign, deep copy with structuredClone, and how to avoid the shared reference trap.

6 min read

Cloning a JavaScript object means creating a new object with the same properties and values as the original, but stored at a different location in memory. The goal is independence: changing the clone should not affect the original.

The challenge is that JavaScript objects can contain nested objects, arrays, dates, and other non-primitive values. Choosing the wrong cloning method leaves you with shared references and hard-to-find bugs.

Why Direct Assignment Does Not Work

A common first attempt at cloning is simply assigning the object to a new variable. That does not create a second object at all. It copies the reference, so both variables still point to the exact same object in memory:

javascriptjavascript
const original = { score: 100 };
const copy = original;
 
copy.score = 0;
 
console.log(original.score); // 0 (changed through copy!)

Both original and copy point to the same object in memory. This is not a clone; it is an alias.

Shallow Copy with the Spread Operator

The spread operator copies all own enumerable properties into a new object:

javascriptjavascript
const original = { name: "Project A", budget: 5000 };
const clone = { ...original };
 
clone.budget = 6000;
 
console.log(original.budget); // 5000 (unchanged)
console.log(clone.budget);    // 6000

For flat objects where every value is a primitive (string, number, boolean, null, undefined), the spread operator gives you a fully independent copy.

The problem appears when values are objects or arrays:

javascriptjavascript
const original = {
  name: "Project A",
  settings: { theme: "dark", notifications: true }
};
 
const clone = { ...original };
 
clone.settings.theme = "light";
 
console.log(original.settings.theme); // "light" (shared reference!)

The top-level name is independent, but settings is a reference to the same nested object. Changing it through the clone changes it everywhere.

Shallow Copy with Object.assign()

Object.assign() behaves the same way as spread for cloning:

javascriptjavascript
const original = { x: 10, y: 20 };
const clone = Object.assign({}, original);
 
clone.x = 99;
console.log(original.x); // 10 (independent)

Like spread, it creates a shallow copy. Nested objects are shared. For more on Object.assign, see the article on /javascript/how-to-use-object-assign-in-javascript-properly.

Deep Copy with structuredClone()

structuredClone() is the modern, built-in way to create a deep copy. It recursively copies all nested values, including objects, arrays, Dates, Maps, Sets, ArrayBuffers, and more:

javascriptjavascript
const original = {
  created: new Date("2026-01-15"),
  items: ["apple", "banana"],
  metadata: { version: 3 }
};
 
const clone = structuredClone(original);
clone.items.push("orange");
 
console.log(original.items); // ["apple", "banana"] (unchanged)

Pushing to the clone's items array never touches the original, because structuredClone copied the array itself instead of just a reference to it. The same holds for the nested metadata object and the Date instance:

javascriptjavascript
clone.metadata.version = 4;
 
console.log(original.metadata.version);       // 3 (unchanged)
console.log(original.created.getFullYear());  // 2026 (unchanged)

Every level is independent. structuredClone() is supported in all modern browsers and Node.js 17+. It handles most built-in types correctly.

What structuredClone cannot clone:

TypeBehavior
FunctionsThrows a DataCloneError
DOM nodesThrows a DataCloneError
Class instances with methodsMethods are lost
SymbolsThrows a DataCloneError
WeakMap / WeakSetThrows a DataCloneError

For plain data objects, arrays, Dates, Maps, and Sets, structuredClone() is the best choice.

Deep Copy with JSON Methods

Before structuredClone(), the common deep-copy trick was JSON.parse(JSON.stringify(obj)):

javascriptjavascript
const original = { name: "Doc", pages: 42 };
const clone = JSON.parse(JSON.stringify(original));
 
clone.pages = 99;
console.log(original.pages); // 42 (independent)

This works for simple data, but converting an object to a JSON string and back silently drops or corrupts several value types along the way. Dates and functions are the two most common surprises:

javascriptjavascript
const original = { created: new Date(), callback: () => "hello" };
const clone = JSON.parse(JSON.stringify(original));
 
console.log(clone.created);  // "2026-07-15T..." (string, not Date)
console.log(clone.callback); // undefined (function lost)

undefined properties and NaN do not survive the round trip either, since neither one has a valid representation in JSON:

javascriptjavascript
const data = { id: undefined, count: NaN };
const clone = JSON.parse(JSON.stringify(data));
 
console.log(clone.id);    // undefined (property removed entirely)
console.log(clone.count); // null (NaN becomes null)

Use JSON methods only when you are certain the object contains only strings, numbers, booleans, arrays, and plain nested objects. For anything else, use structuredClone().

Quick Decision Guide

Your data containsUse
Only primitives (strings, numbers, booleans)Spread { ...obj }
Nested objects, arrays, DatesstructuredClone(obj)
Functions, class instances, DOM nodesManual copy or a library
Unknown shape from an APIstructuredClone(obj) (safest)

Common Mistake: Accidental Mutation After a Shallow Copy

The most frequent cloning bug is using spread on an object with nested arrays and then pushing or splicing, expecting the original to be safe:

javascriptjavascript
const template = { tags: ["js", "web"] };
const copy = { ...template };
 
copy.tags.push("css");
 
console.log(template.tags); // ["js", "web", "css"] (mutated!)

The tags array is shared. If you need to modify nested arrays or objects independently, use structuredClone() or spread at every level manually. For a closer look at that manual spreading technique, see the article on /javascript/copying-nested-objects-with-the-js-spread-operator.

Rune AI

Rune AI

Key Insights

  • Shallow copy (spread or Object.assign) copies only top-level properties. Nested objects are shared.
  • Deep copy with structuredClone() recursively copies all levels and handles most built-in types.
  • JSON.parse(JSON.stringify()) works for simple data but loses functions, Dates, and special objects.
  • Always consider whether you need a shallow or deep copy before choosing a method.
  • structuredClone() is available in all modern browsers and Node.js 17+.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between a shallow copy and a deep copy?

A shallow copy duplicates only the top-level properties. Nested objects are shared by reference. A deep copy recursively duplicates every level, so nested objects are fully independent.

Does the spread operator create a deep copy?

No. const copy = { ...original } creates a shallow copy. Top-level primitives are independent, but nested objects and arrays are shared between the original and the copy.

Can I use JSON.parse(JSON.stringify(obj)) to deep clone?

Yes, but with caveats. It loses functions, undefined values, Date objects (converted to strings), and special types like Map and Set. For most modern use cases, structuredClone() is the better choice.

Conclusion

Cloning an object correctly depends on how deep you need to go. For flat objects with only primitives, the spread operator is enough. For nested data, use structuredClone() for a proper deep copy that handles most JavaScript types. Avoid the shared reference trap by picking the right tool for your data shape.