Deep Copy in JavaScript

Master the art of creating independent copies of complex JavaScript objects and arrays using modern and legacy approaches

Understanding Deep Copy

Deep copying is one of those JavaScript concepts that seems simple until you encounter a nested object that mysteriously mutates across your application. Understanding deep copy isn't just academic--it's essential for building predictable, bug-free applications.

A deep copy of an object is a copy whose properties do not share the same references (point to the same underlying values) as those of the source object. This means when you change either the source or the copy, you can be assured you're not causing the other object to change too. Proper deep copy implementation is foundational to immutable state management practices that prevent hard-to-track bugs in complex applications.

The Shallow Copy Problem

Shallow copies only copy primitive values at the top level. When your object contains nested objects or arrays, these are not copied--they're shared between the original and the copy.

// Shallow copy with spread syntax
const original = { name: 'Apple', colors: ['red', 'green'] };
const shallow = { ...original };

// Modify nested array in the shallow copy
shallow.colors[0] = 'blue';

console.log(original.colors[0]); // 'blue' - Original is affected!

This shared reference behavior is the source of many bugs in JavaScript applications, especially in state management, form handling, and data transformation pipelines. Understanding the difference between shallow and deep copy is essential for any developer working with complex data structures.

Shallow Copy Shared Reference Problem
1// Shallow copy creates shared nested references2const original = { items: [{ id: 1, name: 'Task 1' }] };3const shallowCopy = { ...original };4 5// Modify nested object in the copy6shallowCopy.items[0].completed = true;7 8// Original is also modified!9console.log(original.items[0].completed); // true10 11// Even top-level primitives are independent12shallowCopy.newProp = 'new';13console.log(original.newProp); // undefined

Modern JavaScript: structuredClone()

The structuredClone() method is the modern standard for deep copying in JavaScript, available across all modern browsers since 2022. It creates a deep clone using the structured clone algorithm.

Key Advantages

  • Handles circular references correctly
  • Supports more data types including Error, Map, Set, typed arrays
  • Native performance - faster than JSON serialization
  • Transfer capability for efficient ArrayBuffer handling
  • No external dependencies required

This native approach leverages browser-level optimizations that performance-focused development teams appreciate for eliminating external library dependencies while maintaining excellent runtime characteristics.

structuredClone() Basic Usage
1// Basic deep copy2const original = { 3 name: 'John', 4 address: { city: 'NYC', zip: '10001' } 5};6const clone = structuredClone(original);7 8// Verify independence9console.log(clone === original); // false - different objects10console.log(clone.address === original.address); // false - nested also independent11 12// Circular reference handling (works!)13const obj = { name: 'circular' };14obj.self = obj;15const cloned = structuredClone(obj); // No error thrown16console.log(cloned.self === cloned); // true - circular reference preserved17 18// Transfer ArrayBuffers for memory efficiency19const buffer = new ArrayBuffer(16);20const uint8 = new Uint8Array(buffer);21uint8[0] = 42;22 23const transferred = structuredClone(uint8, { transfer: [buffer] });24console.log(uint8.byteLength); // 0 - buffer was transferred25console.log(transferred[0]); // 42 - data is available in cloned array

The JSON.parse(JSON.stringify()) Approach

This legacy approach serializes objects to JSON strings and parses them back, creating a deep copy in the process.

When It Works

  • Plain objects with primitive values
  • Arrays of primitives
  • Simple nested structures

When It Fails

  • Dates → become strings
  • Functions → are lost
  • undefined → properties are omitted
  • Circular references → throw TypeError
  • Symbols → are lost
  • RegExp, Map, Set → become empty objects

While the JSON method is simple and dependency-free, its limitations make it suitable only for specific use cases where you know your data structure ahead of time.

JSON Method Limitations
1// Dates become strings2const withDate = { created: new Date('2025-01-01') };3const fromJson = JSON.parse(JSON.stringify(withDate));4console.log(typeof fromJson.created); // 'string', not Date!5 6// Functions are lost7const withFunc = { greet: () => 'hello', name: 'John' };8const noFuncs = JSON.parse(JSON.stringify(withFunc));9console.log(noFuncs.greet); // undefined10 11// undefined values are omitted12const withUndefined = { a: 1, b: undefined, c: 3 };13console.log(JSON.stringify(withUndefined)); // {"a":1,"c":3}14 15// Circular references throw16const circular = {};17circular.self = circular;18JSON.stringify(circular); // TypeError: cyclic object value

Lodash cloneDeep: The Comprehensive Solution

For maximum compatibility and comprehensive type support, lodash's cloneDeep function handles virtually all JavaScript types, including those that structuredClone() cannot process.

Advantages

  • Preserves property descriptors (getters/setters)
  • Handles all built-in types correctly
  • Circular reference support
  • Maintains prototype chains
  • Battle-tested and reliable

Trade-offs

  • Adds external dependency
  • Larger bundle size
  • Slightly slower than native methods

For projects requiring robust JavaScript development across legacy and modern browsers, lodash provides the most reliable deep copy solution despite the bundle size trade-off.

Lodash cloneDeep for Complex Objects
1const _ = require('lodash');2 3// Complex object with various types4const complex = {5 date: new Date(),6 regex: /test/gi,7 map: new Map([['key', 'value']]),8 set: new Set([1, 2, 3]),9 nested: { deeper: { value: true } },10 fn: function greet() { return 'hello'; }11};12 13// All types preserved, including the function!14const cloned = _.cloneDeep(complex);15console.log(cloned.date instanceof Date); // true16console.log(cloned.regex instanceof RegExp); // true17console.log(cloned.map instanceof Map); // true18console.log(typeof cloned.fn); // 'function'19console.log(cloned.nested.deeper.value); // true20 21// Nested structure is fully independent22cloned.nested.deeper.value = false;23console.log(complex.nested.deeper.value); // true - original unchanged
Deep Copy Methods Comparison
FeaturestructuredClone()JSON.parse(JSON.stringify())lodash.cloneDeep
Circular referencesYesNo (throws)Yes
Date objectsPreservedBecomes stringPreserved
FunctionsNo (lost)No (lost)Preserved
undefined valuesPreservedOmittedPreserved
Map/SetPreservedEmpty objectPreserved
Getters/SettersNo (lost)No (lost)Preserved
Native performanceYesNo (serialization)No (library)
No dependenciesYesYesNo

Real-World Patterns

State Management (Redux-style)

// Create immutable state updates
function updateItem(state, itemId, changes) {
 return structuredClone(state).map(item => {
 if (item.id === itemId) {
 return { ...item, ...changes };
 }
 return item;
 });
}

API Response Caching

const cache = new Map();

async function fetchWithCache(url) {
 if (cache.has(url)) {
 return structuredClone(cache.get(url));
 }
 
 const response = await fetch(url);
 const data = await response.json();
 cache.set(url, structuredClone(data));
 return data;
}

Form Draft Preservation

function saveFormDraft(formState) {
 const draft = structuredClone(formState);
 draft.savedAt = new Date().toISOString();
 draft.status = 'draft';
 localStorage.setItem('formDraft', JSON.stringify(draft));
 return draft;
}

These patterns are essential for building robust applications that handle data predictably, whether you're working with client-side state management or server-side data processing.

Key Takeaways

Choose the right deep copy method for your use case

Use structuredClone() by Default

Native, fast, and handles most modern JavaScript types. Your go-to choice for new code.

JSON Methods for Simple Data

Only when you know your data is JSON-safe and doesn't contain dates, functions, or circular refs.

Lodash for Maximum Compatibility

When you need to clone functions, preserve getters/setters, or support legacy browsers.

Test Edge Cases

Always verify your deep copy handles circular references, special types, and large data structures.

Frequently Asked Questions

Deep Copy Methods

3

Main approaches

2022+

structuredClone()

1

Recommended

Need Help with JavaScript?

Our web development team specializes in building performant, scalable applications using modern JavaScript patterns.