Copying Objects in JavaScript
Practical guide to deep copy object javascript: reference vs shallow copy, Object.assign and spread pitfalls, JSON.parse(JSON.stringify()) limits, structuredClone usage, handling methods, prototypes, and circular references.
Drake Nguyen
Founder · System Architect
Introduction
Objects are central to JavaScript and understanding how to copy them safely is essential. When you use the assignment operator you don’t create a new object — you copy a reference. That’s why questions like how to deep copy an object in javascript and deep copy object javascript matter: choosing the wrong approach can introduce bugs related to shared references, broken immutability, or lost methods and prototypes.
References vs copies
Simple assignment passes a reference to the same object. Mutating the value through any reference affects the original. For example:
let obj = { a: 1, b: 2 };
let copy = obj;
obj.a = 5;
console.log(copy.a); // 5 — not a true copy
Because copy and obj point at the same object, immutability is gone. If you need independent objects, you must create a shallow copy or a deep copy depending on the shape of your data.
Shallow copy: what it is and common pitfalls
A shallow copy duplicates top-level properties but preserves references for nested objects. That means nested objects remain shared between the original and the copy.
Object.assign() and spread operator (shallow)
Object.assign() and the spread syntax are common ways to create a shallow copy. They copy own enumerable properties to a new object:
const obj = { a: 1, b: { c: 2 } };
const shallow = Object.assign({}, obj);
// or
const shallow2 = { ...obj };
shallow.b.c = 99;
console.log(obj.b.c); // 99 — nested object is still shared
Shallow copy pitfalls include:
- Shared nested objects (see above)
- Non-enumerable properties and prototype properties are not copied
- Property descriptors (writable/configurable/enumerable) aren’t preserved
Deep copy: complete duplication
A deep copy recursively clones nested objects so the copy shares nothing with the original. There are several strategies with trade-offs in coverage, performance, and browser compatibility.
JSON.parse(JSON.stringify())
A popular simple approach is JSON.stringify + JSON.parse. This deep copies plain data objects (numbers, strings, arrays, plain objects) but has important limitations:
- Functions, undefined and Symbol values are lost
- Dates become strings, RegExp and Map/Set are not preserved
- Circular references cause an error
- Prototype chain and property descriptors are not preserved
const a = { x: 1, b: { c: 2 } };
const deep = JSON.parse(JSON.stringify(a));
deep.b.c = 50;
console.log(a.b.c); // 2 — nested object copied
// But methods are removed:
const withMethod = { fn() { return true } };
const stripped = JSON.parse(JSON.stringify(withMethod));
console.log(stripped.fn); // undefined
Modern: structuredClone (recommended where available)
In modern environments, structuredClone implements the Structured Clone Algorithm and can deep copy many built-in types, preserving more than JSON can. It handles circular references, Date, RegExp, Map, Set, ArrayBuffer, typed arrays, and more. Examples:
const original = { d: new Date(), arr: [1, 2], nested: { v: 1 } };
const copy = structuredClone(original);
copy.nested.v = 42;
console.log(original.nested.v); // 1 — safe deep copy
Notes about structuredClone:
- Does not clone functions or copy prototypes (it clones data)
- Widely supported in modern browsers and Node.js (check your target environment)
- Use a polyfill or library for older environments
Custom deep clone (for special requirements)
When you need to preserve prototypes, property descriptors, methods, or clone unusual data, a custom deep clone or a specialized library is appropriate. A robust custom implementation typically:
- Walks the object graph recursively
- Tracks references to handle circular references
- Copies property descriptors and preserves prototypes if required
// Simplified sketch (real implementations need careful feature handling)
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
const copy = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
map.set(obj, copy);
Reflect.ownKeys(obj).forEach(key => {
const desc = Object.getOwnPropertyDescriptor(obj, key);
if (desc.get || desc.set) {
Object.defineProperty(copy, key, desc);
} else {
desc.value = deepClone(desc.value, map);
Object.defineProperty(copy, key, desc);
}
});
return copy;
}
This approach preserves prototypes and property descriptors but is more complex and slower than structuredClone or JSON-based cloning.
Copying methods and prototypes
Methods are function-valued properties. JSON.parse(JSON.stringify()) strips them. Object.assign and spread copy methods only as values (shallowly). If you need to maintain prototype behavior or non-enumerable properties, use Object.create plus explicit property descriptor copying (as in the custom deep clone above) or a library that documents handling of prototypes and descriptors.
Circular references
JSON-based cloning fails for circular structures. structuredClone and correctly implemented recursive cloning using a WeakMap handle circular references by tracking seen objects and reusing references in the clone.
Recommendations — best way to clone object in javascript 2026
- Use structuredClone for most data-centric deep copies: it is fast, safe, and handles circular references and many built-in types.
- For plain-data objects where structuredClone is unavailable, JSON.parse(JSON.stringify()) is a quick fallback, but be aware of its limits (no functions, Dates, Maps, Sets, or circular references).
- When you must preserve prototypes, methods, non-enumerable properties, or property descriptors, prefer a well-tested custom clone or a library (for example, lodash.clonedeep or a vetted alternative) and ensure it meets your EEAT and security requirements.
Tip: Always choose the cloning strategy that matches the data you actually use. Deep cloning every object by default can be expensive and unnecessary — consider immutability patterns or targeted copies when possible.
Conclusion
Understanding deep copy vs shallow copy in javascript helps you avoid hard-to-detect bugs caused by shared references and lost data during serialization. For deep copy object javascript tasks in modern code, structuredClone is the best-first choice; fallback strategies and custom clones remain important for edge cases like preserving prototypes, methods, or copying circular object graphs.