How to Clone a JavaScript Object Without Errors

Learn every method to clone JavaScript objects correctly: spread operator, Object.assign, structuredClone, and JSON.parse. Avoid mutation bugs forever.

JavaScriptbeginner
11 min read

Copying objects in JavaScript is one of the most common sources of bugs. Because objects are reference types, assigning one variable to another does not create a copy. It creates a second reference to the same data. Modifying the "copy" also modifies the original, leading to subtle bugs that are hard to track down.

This guide covers every cloning method available in JavaScript, explains the difference between shallow and deep copies, and shows you which method to use in each situation.

Why You Need to Clone Objects

When you assign an object to a new variable, both variables point to the same object in memory:

javascriptjavascript
const original = { name: "Alice", age: 30 };
const reference = original;
 
reference.age = 31;
console.log(original.age); // 31 - original was mutated!

This is not a bug in JavaScript. It is how reference types work. Primitives (numbers, strings, booleans) are copied by value, but objects, arrays, and functions are passed by reference. To create an independent copy, you need to explicitly clone the object.

Method 1: Spread Operator (Shallow Clone)

The spread operator (...) is the most common and readable way to create a shallow clone:

javascriptjavascript
const original = { name: "Alice", age: 30, active: true };
const clone = { ...original };
 
clone.name = "Bob";
console.log(original.name); // "Alice" - original untouched
console.log(clone.name);    // "Bob"

You can also add or override properties during the clone:

javascriptjavascript
const defaults = { theme: "light", fontSize: 14, lang: "en" };
const userPrefs = { theme: "dark", fontSize: 18 };
 
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: "dark", fontSize: 18, lang: "en" }

Limitation: Nested Objects Are NOT Cloned

Spread only copies the top-level properties. Nested objects are still shared references:

javascriptjavascript
const original = {
  name: "Alice",
  address: { city: "Portland", state: "OR" }
};
 
const clone = { ...original };
clone.address.city = "Seattle";
 
console.log(original.address.city); // "Seattle" - BUG! Nested object was shared

Method 2: Object.assign() (Shallow Clone)

Object.assign() works similarly to the spread operator for cloning:

javascriptjavascript
const original = { name: "Alice", age: 30, active: true };
const clone = Object.assign({}, original);
 
clone.name = "Bob";
console.log(original.name); // "Alice" - original untouched

The key difference from spread: Object.assign mutates its first argument (the target), while spread always creates a new object. If you accidentally pass an existing object as the target, it gets modified:

javascriptjavascript
const existing = { x: 1 };
const source = { y: 2, z: 3 };
 
// This mutates existing!
Object.assign(existing, source);
console.log(existing); // { x: 1, y: 2, z: 3 }

Like spread, Object.assign only creates shallow copies. Nested objects remain shared.

Method 3: structuredClone() (Deep Clone)

structuredClone() is the modern, built-in way to create deep clones. It recursively copies all nested objects:

javascriptjavascript
const original = {
  name: "Alice",
  address: { city: "Portland", state: "OR" },
  hobbies: ["reading", "hiking"],
  metadata: {
    scores: { math: 95, science: 88 }
  }
};
 
const clone = structuredClone(original);
 
// Modify the clone deeply
clone.address.city = "Seattle";
clone.hobbies.push("coding");
clone.metadata.scores.math = 100;
 
// Original is completely untouched
console.log(original.address.city);      // "Portland"
console.log(original.hobbies.length);     // 2
console.log(original.metadata.scores.math); // 95

What structuredClone Supports

Data TypeSupported
Plain objectsYes
ArraysYes
Date objectsYes
RegExpYes
Map, SetYes
ArrayBuffer, TypedArraysYes
FunctionsNo (throws error)
DOM elementsNo
Class instances (lose prototype)Partial
Symbol propertiesNo (ignored)
javascriptjavascript
// Works with Date, Map, Set
const data = {
  created: new Date("2026-01-01"),
  tags: new Set(["js", "web"]),
  scores: new Map([["math", 95], ["science", 88]])
};
 
const clone = structuredClone(data);
console.log(clone.created instanceof Date); // true
console.log(clone.tags instanceof Set);     // true
console.log(clone.scores instanceof Map);   // true

Method 4: JSON.parse(JSON.stringify()) (Deep Clone, Limited)

Before structuredClone existed, the JSON round-trip was the go-to deep clone method:

javascriptjavascript
const original = {
  name: "Alice",
  address: { city: "Portland" },
  scores: [95, 88, 92]
};
 
const clone = JSON.parse(JSON.stringify(original));
clone.address.city = "Seattle";
console.log(original.address.city); // "Portland" - deep clone works

What JSON Round-Trip Destroys

This method serializes to a JSON string and back, which means it loses everything that JSON does not support:

javascriptjavascript
const data = {
  date: new Date("2026-01-01"),      // Becomes a string
  regex: /hello/gi,                  // Becomes empty object {}
  func: () => "hi",                  // Completely removed
  undef: undefined,                  // Completely removed
  nan: NaN,                          // Becomes null
  infinity: Infinity,                // Becomes null
  map: new Map([["a", 1]]),          // Becomes empty object {}
  set: new Set([1, 2, 3])            // Becomes empty object {}
};
 
const clone = JSON.parse(JSON.stringify(data));
console.log(clone.date);     // "2026-01-01T00:00:00.000Z" (string, not Date)
console.log(clone.regex);    // {}
console.log(clone.func);     // undefined (removed)
console.log(clone.undef);    // undefined (removed from object)
console.log(clone.nan);      // null

Use JSON round-trip only when you know your data contains only JSON-safe types (strings, numbers, booleans, null, plain objects, and arrays).

Method Comparison Table

MethodDepthFunctionsDate/Map/SetSpeedUse Case
Spread { ...obj }ShallowCopies referenceCopies referenceFastestSimple flat objects
Object.assign()ShallowCopies referenceCopies referenceFastMerging with existing target
structuredClone()DeepThrows errorPreserves correctlyMediumGeneral deep cloning
JSON.parse(JSON.stringify())DeepStrips themConverts/losesSlowJSON-safe data only

When to Use Each Method

javascriptjavascript
// Flat object with no nesting - use spread
const flat = { a: 1, b: "two", c: true };
const clone1 = { ...flat };
 
// Nested object with plain data - use structuredClone
const nested = { user: { name: "Alice", address: { city: "Portland" } } };
const clone2 = structuredClone(nested);
 
// Object with functions - manual clone required
const withMethods = {
  name: "Alice",
  greet() { return `Hi, I'm ${this.name}`; }
};
const clone3 = { ...withMethods }; // Spread copies the function reference
 
// Merging multiple sources - use Object.assign or spread
const merged = { ...defaults, ...overrides };

Real-World Example: State Management Without Mutation

Immutable state updates are critical in frameworks like React. Here is how cloning prevents mutation bugs:

javascriptjavascript
function updateUserProfile(state, updates) {
  // Deep clone the state to avoid mutating the original
  const newState = structuredClone(state);
 
  // Apply updates safely
  if (updates.name) newState.user.name = updates.name;
  if (updates.email) newState.user.email = updates.email;
 
  if (updates.address) {
    newState.user.address = {
      ...newState.user.address,
      ...updates.address
    };
  }
 
  if (updates.addSkill) {
    newState.user.skills.push(updates.addSkill);
  }
 
  newState.lastModified = new Date();
  return newState;
}
 
const currentState = {
  user: {
    name: "Alice",
    email: "alice@example.com",
    address: { city: "Portland", state: "OR" },
    skills: ["JavaScript", "React"]
  },
  lastModified: new Date("2026-01-01")
};
 
const nextState = updateUserProfile(currentState, {
  name: "Alice Johnson",
  address: { city: "Seattle" },
  addSkill: "TypeScript"
});
 
// Original state is preserved
console.log(currentState.user.name);         // "Alice"
console.log(currentState.user.address.city); // "Portland"
console.log(currentState.user.skills.length); // 2

Common Mistakes to Avoid

Thinking Assignment Creates a Copy

javascriptjavascript
const original = { x: 1, y: 2 };
const notACopy = original;  // This is a reference, NOT a copy
 
notACopy.x = 99;
console.log(original.x); // 99 - mutated!
 
// Fix: explicitly clone
const actualCopy = { ...original };

Using Spread for Deep Objects

javascriptjavascript
const state = {
  user: { preferences: { theme: "dark" } }
};
 
// WRONG - nested objects are shared
const newState = { ...state };
newState.user.preferences.theme = "light";
console.log(state.user.preferences.theme); // "light" - BUG!
 
// CORRECT - use structuredClone for deep data
const safeState = structuredClone(state);

Cloning Objects with Circular References

javascriptjavascript
const obj = { name: "circular" };
obj.self = obj; // Circular reference
 
// JSON method throws
// JSON.parse(JSON.stringify(obj)); // TypeError: circular
 
// structuredClone handles it correctly
const clone = structuredClone(obj);
console.log(clone.self === clone); // true (circular ref preserved)

Best Practices

  1. Use spread { ...obj } for flat objects. It is the fastest and most readable option for objects without nesting.
  2. Use structuredClone() for nested data. It is the standard deep clone method with the best type support.
  3. Avoid JSON round-trip for new code. structuredClone does everything JSON round-trip does and more, without destroying types.
  4. Clone before mutating shared state. In any architecture where multiple parts of your code reference the same object, clone before making changes.
  5. Watch for functions in cloned objects. Neither structuredClone nor JSON handles functions. Use spread if you need to preserve method references.
Rune AI

Rune AI

Key Insights

  • Spread { ...obj } is best for flat objects: fast, readable, and creates a clean shallow copy in one expression
  • structuredClone() is the deep clone standard: handles nested objects, Date, Map, Set, and even circular references correctly
  • JSON round-trip is legacy: it destroys Date objects, functions, Map, Set, undefined, and NaN, so prefer structuredClone for new projects
  • Assignment is not cloning: const b = a creates a reference, not a copy, so mutations to b affect a
  • Clone before mutating shared state: immutable update patterns prevent the hardest-to-debug category of JavaScript bugs
RunePowered by Rune AI

Frequently Asked Questions

Does structuredClone work in all browsers?

Yes, `structuredClone()` is supported in all modern browsers (Chrome 98+, Firefox 94+, Safari 15.4+, Edge 98+) and Node.js 17+. For older environments, use [a polyfill](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) or fall back to the JSON method for simple data.

How do I deep clone an object with functions?

No built-in method handles functions in deep clones. The practical approach is to separate data from behavior: clone the data with `structuredClone()`, then reattach methods manually. For class instances, implement a custom `clone()` method on the class that knows how to recreate itself.

Is the spread operator faster than Object.assign?

In modern JavaScript engines, spread and `Object.assign` perform nearly identically for shallow cloning. The spread operator is marginally faster in some benchmarks because engines optimize the `{ ...obj }` pattern specifically. For practical purposes, choose based on readability, not speed.

Can I clone an array of objects?

Yes. For a shallow clone of the array, use `[...array]` or `array.slice()`. For a deep clone where nested objects inside each array element should also be independent, use `structuredClone(array)`. This is common when working with lists of records from an API.

What is the difference between shallow and deep copy?

[shallow copy](/tutorials/programming-languages/javascript/shallow-copy-vs-deep-copy-in-javascript-objects) duplicates only the top-level properties. Nested objects and arrays remain shared between the original and the clone. A deep copy recursively duplicates every level of nesting, creating completely independent data. Shallow copies are faster but can cause mutation bugs with nested data.

Conclusion

Cloning JavaScript objects correctly requires understanding which method matches your data structure. Spread and Object.assign handle flat objects efficiently but share nested references. structuredClone() is the modern standard for deep cloning with full type support. The JSON round-trip method still works for simple, JSON-safe data but should be replaced with structuredClone in new code.