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.
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:
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:
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:
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:
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 sharedMethod 2: Object.assign() (Shallow Clone)
Object.assign() works similarly to the spread operator for cloning:
const original = { name: "Alice", age: 30, active: true };
const clone = Object.assign({}, original);
clone.name = "Bob";
console.log(original.name); // "Alice" - original untouchedThe 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:
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:
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); // 95What structuredClone Supports
| Data Type | Supported |
|---|---|
| Plain objects | Yes |
| Arrays | Yes |
| Date objects | Yes |
| RegExp | Yes |
| Map, Set | Yes |
| ArrayBuffer, TypedArrays | Yes |
| Functions | No (throws error) |
| DOM elements | No |
| Class instances (lose prototype) | Partial |
| Symbol properties | No (ignored) |
// 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); // trueMethod 4: JSON.parse(JSON.stringify()) (Deep Clone, Limited)
Before structuredClone existed, the JSON round-trip was the go-to deep clone method:
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 worksWhat JSON Round-Trip Destroys
This method serializes to a JSON string and back, which means it loses everything that JSON does not support:
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); // nullUse 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
| Method | Depth | Functions | Date/Map/Set | Speed | Use Case |
|---|---|---|---|---|---|
Spread { ...obj } | Shallow | Copies reference | Copies reference | Fastest | Simple flat objects |
Object.assign() | Shallow | Copies reference | Copies reference | Fast | Merging with existing target |
structuredClone() | Deep | Throws error | Preserves correctly | Medium | General deep cloning |
JSON.parse(JSON.stringify()) | Deep | Strips them | Converts/loses | Slow | JSON-safe data only |
When to Use Each Method
// 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:
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); // 2Common Mistakes to Avoid
Thinking Assignment Creates a Copy
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
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
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
- Use spread
{ ...obj }for flat objects. It is the fastest and most readable option for objects without nesting. - Use
structuredClone()for nested data. It is the standard deep clone method with the best type support. - Avoid JSON round-trip for new code.
structuredClonedoes everything JSON round-trip does and more, without destroying types. - Clone before mutating shared state. In any architecture where multiple parts of your code reference the same object, clone before making changes.
- Watch for functions in cloned objects. Neither
structuredClonenor JSON handles functions. Use spread if you need to preserve method references.
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
structuredClonefor new projects - Assignment is not cloning:
const b = acreates a reference, not a copy, so mutations tobaffecta - Clone before mutating shared state: immutable update patterns prevent the hardest-to-debug category of JavaScript bugs
Frequently Asked Questions
Does structuredClone work in all browsers?
How do I deep clone an object with functions?
Is the spread operator faster than Object.assign?
Can I clone an array of objects?
What is the difference between shallow and deep copy?
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.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.