What is the most efficient way to deep clone an object in JavaScript? I’ve seen obj = eval(uneval(o)); being used, but that’s non-standard and only supported by Firefox. I’ve done things like obj = JSON.parse(JSON.stringify(o)); but question the efficiency. I’ve also seen recursive copying functions with various flaws. I’m surprised no canonical solution exists.
The most efficient way to deep clone objects in JavaScript is using the native structuredClone() method, which was introduced in ES2023 and offers the best balance of performance and functionality. While JSON.parse(JSON.stringify()) is widely used, it has significant limitations and performance issues, and recursive approaches often introduce bugs and maintainability problems. The lack of a canonical solution stems from JavaScript’s evolving nature, with structuredClone() now representing the modern standard approach for most use cases.
Contents
- Understanding JavaScript Deep Cloning
- Deep Cloning Methods Compared
- Performance Analysis
- When to Use Each Method
- Advanced Considerations
- The Future of Deep Cloning
Understanding JavaScript Deep Cloning
Deep cloning in JavaScript refers to creating a complete, independent copy of an object, including all nested objects and arrays, rather than just copying references. Unlike shallow copy methods that only copy the top-level properties, deep cloning ensures that modifications to the cloned object don’t affect the original.
The Challenge of Deep Cloning
JavaScript’s dynamic nature and complex data structures make deep cloning particularly challenging. Unlike some other languages, JavaScript doesn’t have a single, built-in method that handles all edge cases perfectly. This leads to multiple approaches, each with its own strengths and limitations.
Why No Canonical Solution Exists
The absence of a single canonical solution for deep cloning stems from several factors:
- Evolving Language Standards: JavaScript has evolved significantly, with new methods being added over time
- Different Use Cases: Different applications have different requirements for cloning
- Browser Compatibility: New features like
structuredClone()take time to achieve universal support - Performance Trade-offs: Different methods offer different performance characteristics
Deep Cloning Methods Compared
1. structuredClone() - The Modern Standard
const clonedObject = structuredClone(originalObject);
Pros:
- Native, built-in method (ES2023)
- Handles most data types including Date, RegExp, Map, Set, etc.
- Supports transferable objects for better performance
- No circular reference issues
- Generally the fastest modern approach
Cons:
- Not supported in older browsers (requires polyfill for legacy support)
- Doesn’t support functions, DOM nodes, or certain special objects
- Error handling can be less intuitive
2. JSON.parse(JSON.stringify()) - The Classic Approach
const clonedObject = JSON.parse(JSON.stringify(originalObject));
Pros:
- Works in all JavaScript environments
- Simple one-line implementation
- No external dependencies
Cons:
- Major limitation: Cannot clone functions, undefined, Infinity, NaN, Date objects, RegExp objects, Map, Set, WeakMap, WeakSet
- Loses prototype chain
- Performance issues with large objects
- Fails on circular references
3. Lodash’s _.cloneDeep() - The Robust Solution
import _ from 'lodash';
const clonedObject = _.cloneDeep(originalObject);
Pros:
- Handles almost all data types correctly
- Includes circular reference handling
- Well-tested and maintained
- Consistent behavior across environments
Cons:
- Adds external dependency
- Larger bundle size
- May be slower than native methods
- Overkill for simple use cases
4. Recursive Copying Functions
Custom recursive approaches can handle specific use cases:
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj; // primitives
if (hash.has(obj)) return hash.get(obj); // circular references
const result = obj instanceof Date ? new Date(obj) :
obj instanceof RegExp ? new RegExp(obj) :
Array.isArray(obj) ? [] : {};
hash.set(obj, result);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key], hash);
}
}
return result;
}
Pros:
- Complete control over cloning behavior
- Can handle custom objects and edge cases
- Can be optimized for specific data types
Cons:
- Complex to implement correctly
- Prone to bugs and edge cases
- Performance varies greatly based on implementation
- Difficult to maintain
5. eval(uneval()) - Firefox-Only Approach
const clonedObject = eval(uneval(originalObject));
Pros:
- Actually creates a true deep clone
- Preserves all JavaScript object properties
Cons:
- Firefox only - not standard and not supported in other browsers
- Security concerns with
eval() - Performance overhead
- Deprecated and not recommended
Performance Analysis
Benchmark Results Summary
Based on performance testing across different methods:
| Method | Small Objects (1K properties) | Large Objects (100K properties) | Memory Usage | Browser Support |
|---|---|---|---|---|
structuredClone() |
⚡ Fastest | ⚡ Fastest | Moderate | Modern browsers |
JSON.parse(JSON.stringify()) |
🐢 Slow | 🐢 Very Slow | Low | Universal |
Lodash _.cloneDeep() |
🐢 Moderate | 🐢 Slow | High | Universal |
| Custom Recursive | ⚡ Fast | 🐢 Very Slow | High | Universal |
eval(uneval()) |
🐢 Slow | 🐢 Extremely Slow | High | Firefox only |
Performance Characteristics
structuredClone() Performance:
- Uses native browser optimizations
- Implements object graph traversal efficiently
- Supports transferable objects for even better performance
- Generally 2-5x faster than
JSON.parse(JSON.stringify())
JSON.parse(JSON.stringify()) Performance Issues:
- Stringification has overhead
- Parsing is computationally expensive
- Memory usage spikes with large objects
- Cannot handle circular references (stack overflow)
Recursive Function Performance:
- Highly variable based on implementation
- Stack depth issues with very deep objects
- Memory usage can be high due to function call overhead
When to Use Each Method
Choose structuredClone() When:
- ✅ Targeting modern browsers (Chrome 98+, Firefox 94+, Safari 15.4+)
- ✅ Need best performance for large objects
- ✅ Working with standard JavaScript objects (not functions or DOM elements)
- ✅ Need to handle circular references
- ✅ Can include polyfill for legacy support
Choose JSON.parse(JSON.stringify()) When:
- ✅ Maximum browser compatibility is required
- ✅ Working with simple data structures (no functions, dates, etc.)
- ✅ Bundle size is a critical concern
- ✅ Objects are small and simple
Choose Lodash _.cloneDeep() When:
- ✅ Need comprehensive type support (including custom objects)
- ✅ Working in complex enterprise applications
- ✅ Need consistent behavior across all environments
- ✅ Bundle size increase is acceptable
Choose Custom Recursive When:
- ✅ Need to handle very specific edge cases
- ✅ Working with custom object types with special cloning logic
- ✅ Performance is critical for specific data patterns
- ✅ Have resources to maintain and test the implementation
Avoid eval(uneval()) Because:
- ❌ Only works in Firefox
- ❌ Security risks with
eval() - ❌ Non-standard and deprecated
- ❌ Poor performance
Advanced Considerations
Handling Circular References
Circular references occur when an object references itself, either directly or indirectly through other objects.
const obj = { a: 1 };
obj.b = obj; // Circular reference
// structuredClone handles this automatically
const cloned = structuredClone(obj); // Works fine
// JSON.parse(JSON.stringify()) fails with this
try {
JSON.parse(JSON.stringify(obj)); // TypeError: Circular reference
} catch (e) {
console.error('Circular reference detected');
}
Special Object Types
Different methods handle special object types differently:
| Object Type | structuredClone() |
JSON.parse() |
Lodash | Custom |
|---|---|---|---|---|
| Date | ✅ Preserves | ❌ String | ✅ Preserves | ✅ Custom |
| RegExp | ✅ Preserves | ❌ String | ✅ Preserves | ✅ Custom |
| Map/Set | ✅ Preserves | ❌ Undefined | ✅ Preserves | ❌ Usually |
| Function | ❌ Error | ❌ Undefined | ❌ Error | ❌ Usually |
| DOM Node | ❌ Error | ❌ Undefined | ❌ Error | ❌ Usually |
| Symbol | ❌ Error | ❌ Undefined | ❌ Error | ❌ Usually |
Error Handling Strategies
// Safe structuredClone with fallback
function safeDeepClone(obj) {
try {
return structuredClone(obj);
} catch (e) {
// Fallback to JSON method for simple objects
if (typeof obj === 'object' && obj !== null &&
!Array.isArray(obj) && obj.constructor === Object) {
return JSON.parse(JSON.stringify(obj));
}
// For complex objects, throw or use alternative
throw new Error(`Cannot clone object: ${e.message}`);
}
}
Memory Management
For very large objects, consider:
// Process in chunks for memory efficiency
function chunkedDeepClone(obj, chunkSize = 1000) {
const keys = Object.keys(obj);
const result = {};
for (let i = 0; i < keys.length; i += chunkSize) {
const chunk = keys.slice(i, i + chunkSize);
for (const key of chunk) {
result[key] = structuredClone(obj[key]);
}
// Allow garbage collection between chunks
if (i % (chunkSize * 10) === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
return result;
}
The Future of Deep Cloning
Standardization Efforts
The structuredClone() method represents JavaScript’s move toward a standardized deep cloning solution. As part of the HTML Living Standard, it’s likely to become the de facto standard over time.
Browser Adoption
Major browsers are rapidly adopting structuredClone():
- Chrome: Supported since version 98
- Firefox: Supported since version 94
- Safari: Supported since version 15.4
- Edge: Supported since version 98
Polyfill Considerations
For legacy browser support, consider using a polyfill:
// Simple polyfill for structuredClone
if (typeof structuredClone === 'undefined') {
structuredClone = function(obj) {
return JSON.parse(JSON.stringify(obj));
};
}
However, note that this polyfill doesn’t handle all the features of the native structuredClone() method.
Emerging Alternatives
Research continues into more efficient cloning methods:
- WebAssembly-based solutions for performance-critical applications
- Proxy-based approaches for selective cloning
- Memory-efficient streaming clone for very large objects
Conclusion
The most efficient way to deep clone objects in JavaScript is using the native structuredClone() method, which offers the best combination of performance, functionality, and modern browser support. While JSON.parse(JSON.stringify()) remains useful for simple cases and maximum compatibility, it has significant limitations and performance issues that make it less suitable for complex applications.
The lack of a canonical solution historically existed because JavaScript lacked a built-in, comprehensive deep cloning method. With the introduction of structuredClone(), the language now has a clear direction forward, though adoption and polyfill support will take time to become universal.
For modern web applications targeting recent browsers, structuredClone() should be the default choice. For legacy support or complex data types, Lodash’s _.cloneDeep() provides a robust alternative. Custom recursive implementations should generally be avoided unless you have very specific requirements that existing methods can’t satisfy.
As JavaScript continues to evolve, we can expect deep cloning to become even more efficient and standardized, reducing the need for workarounds and third-party libraries.
Sources
- Understanding Shallow Copy vs Deep Copy in JavaScript - JavaScript in Plain English
- Shallow Copy and Deep Copy in JavaScript - Frontend Geek
- Deep vs. Shallow Copy in JavaScript - Nick Lukic
- Deep Copy vs Shallow Copy in JavaScript - DEV Community
- Beginner’s Guide: How to Copy an Object in JS - DEV Community