What is the Most Efficient Way to Deep Clone an Object in JavaScript? Top Methods Compared & Analyzed

In JavaScript, objects and arrays are reference types, meaning variables store references to their memory locations rather than actual values. When you "copy" an object using a shallow clone (e.g., Object.assign() or the spread operator ...), nested objects or arrays still reference the original data. This can lead to unintended side effects: modifying the cloned object might alter the original, and vice versa.

Deep cloning solves this by creating an entirely independent copy of the object, including all nested properties. But with multiple methods available—from simple one-liners to library-based solutions—choosing the right approach depends on your use case, data complexity, and performance needs.

This blog explores the top deep cloning methods in JavaScript, analyzing their pros, cons, edge cases, and efficiency to help you decide which is best for your project.

Table of Contents#

  1. What is Deep Cloning?
  2. Top Methods to Deep Clone an Object
  3. Comparative Analysis: Which Method is Most Efficient?
  4. Conclusion
  5. References

What is Deep Cloning?#

Before diving into methods, let’s clarify:

  • Shallow Clone: Copies the top-level properties of an object. Nested objects/arrays remain referenced to the original.
    Example: const shallowClone = { ...original };

  • Deep Clone: Creates a全新 (brand-new) copy of the object, including all nested objects, arrays, and primitive values. Changes to the clone do not affect the original.

For example, with a nested object:

const original = { a: 1, b: { c: 2 } };
const shallowClone = { ...original };
shallowClone.b.c = 3; 
console.log(original.b.c); // Output: 3 (original modified!)
 
// With deep clone:
const deepClone = deepCloneFunction(original);
deepClone.b.c = 4; 
console.log(original.b.c); // Output: 2 (original unchanged)

Deep cloning is critical for state management (e.g., React, Redux), immutable data patterns, and avoiding side effects in complex applications.

Top Methods to Deep Clone an Object#

Method 1: JSON.parse(JSON.stringify())#

How It Works#

This is the most common "quick and dirty" method. It converts the object to a JSON string with JSON.stringify(), then parses the string back into a new object with JSON.parse().

Example#

const original = { 
  name: "Alice", 
  age: 30, 
  address: { city: "New York", zip: "10001" } 
};
 
const clone = JSON.parse(JSON.stringify(original));
 
// Modify clone
clone.address.city = "Boston";
console.log(original.address.city); // Output: "New York" (original unchanged)

Pros#

  • Simplicity: No external dependencies or complex code—just two built-in functions.
  • No Learning Curve: Easy to remember and implement.

Cons#

  • Limited Data Type Support: Fails to clone many JavaScript-specific types:
    • Function: Lost (JSON ignores functions).
    • Date: Converted to a string (e.g., "2024-01-01T00:00:00.000Z"), not a Date object.
    • RegExp: Becomes an empty object {}.
    • Symbol: Properties with Symbol keys are omitted.
    • undefined: Properties with undefined values are omitted.
    • Map/Set: Converted to empty objects/arrays.
  • Circular References: Throws an error if the object has circular references (e.g., obj.self = obj).
  • Performance: Slow for large objects due to string conversion overhead.

Edge Cases to Avoid#

  • Objects with circular references (e.g., const obj = {}; obj.self = obj;).
  • Non-serializable values (functions, Symbol, undefined).
  • Dates (will become strings, not Date instances).

Method 2: Manual Recursive Function#

How It Works#

A custom recursive function traverses the object, checks the type of each property, and creates a deep copy by recursively cloning nested objects/arrays. This gives you full control over which data types to support.

Example Implementation#

function deepClone(obj) {
  // Handle non-objects and null
  if (obj === null || typeof obj !== "object") return obj;
 
  // Handle Date
  if (obj instanceof Date) {
    return new Date(obj);
  }
 
  // Handle RegExp
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }
 
  // Handle Arrays
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item));
  }
 
  // Handle Objects (plain objects, not classes)
  if (obj.constructor === Object) {
    const clonedObj = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        clonedObj[key] = deepClone(obj[key]); // Recurse
      }
    }
    return clonedObj;
  }
 
  // For other types (e.g., classes, Map, Set), return original or customize
  return obj; // Fallback: return original if unsupported
}
 
// Usage
const original = { 
  date: new Date(), 
  regex: /test/i, 
  hobbies: ["reading", "coding"], 
  address: { city: "Paris" } 
};
 
const clone = deepClone(original);
console.log(clone.date instanceof Date); // Output: true
console.log(clone.regex.source); // Output: "test"

Pros#

  • Customizability: Add support for specific types (e.g., Map, Set, Symbol) as needed.
  • No Dependencies: Works without external libraries.
  • Handles More Types: Can clone Date, RegExp, and nested structures if implemented correctly.

Cons#

  • Complexity: Requires handling edge cases (e.g., null, arrays vs. objects, Date, RegExp).
  • Error-Prone: Easy to miss edge cases (e.g., circular references, Map/Set, or class instances).
  • Maintenance: Needs updates if new data types are introduced.

Enhancements#

To improve this, add support for:

  • Map: new Map([...obj.entries()].map(([k, v]) => [deepClone(k), deepClone(v)]))
  • Set: new Set([...obj].map(item => deepClone(item)))
  • Circular References: Use a WeakMap to track visited objects and avoid infinite loops.

Method 3: Lodash’s _.cloneDeep#

How It Works#

Lodash is a popular utility library with a battle-tested _.cloneDeep method designed to deep clone objects. It handles a wide range of data types and edge cases.

Example#

First, install Lodash:

npm install lodash

Then use _.cloneDeep:

import { cloneDeep } from "lodash";
 
const original = { 
  name: "Bob", 
  pets: new Set(["cat", "dog"]), 
  scores: new Map([["math", 90], ["science", 85]]), 
  nested: { deep: { value: 42 } } 
};
 
const clone = cloneDeep(original);
 
console.log(clone.pets instanceof Set); // Output: true
console.log(clone.scores.get("math")); // Output: 90

Pros#

  • Robust Type Support: Handles Date, RegExp, Map, Set, ArrayBuffer, DataView, circular references, and more.
  • Battle-Tested: Used by millions of projects; thoroughly tested for edge cases.
  • Circular References: Safely clones objects with circular references (e.g., obj.self = obj).

Cons#

  • Dependency Overhead: Adds Lodash as a dependency (≈72KB minified). For small projects, this may be overkill.
  • Bundle Size: If using only _.cloneDeep, consider tree-shaking (via ES modules) to reduce size:
    import cloneDeep from "lodash/cloneDeep"; // Smaller bundle size

Method 4: Native structuredClone (Structured Clone Algorithm)#

How It Works#

The Structured Clone Algorithm is a built-in browser/Node.js feature that natively deep clones objects. It’s exposed via structuredClone() (available in modern browsers and Node.js 17+).

Example#

// Browser or Node.js 17+
const original = { 
  date: new Date(), 
  regex: /hello/g, 
  data: new Uint8Array([1, 2, 3]), 
  nested: { arr: [1, 2, 3] } 
};
 
const clone = structuredClone(original);
 
console.log(clone.date instanceof Date); // Output: true
console.log(clone.regex.flags); // Output: "g"
console.log(clone.data instanceof Uint8Array); // Output: true

Supported Types#

  • Primitives: string, number, boolean, null, undefined, Symbol (limited support).
  • Complex Types: Date, RegExp, Array, Object (plain objects), Map, Set, ArrayBuffer, DataView, Blob, File, etc.

Pros#

  • Native & Fast: Built into the JavaScript engine, so no external dependencies and optimized for performance.
  • Broad Type Support: Handles more types than JSON.parse(JSON.stringify()) (e.g., Map, Set, Date).
  • Circular References: Safely clones objects with circular references.

Cons#

  • Limited Support for Older Environments:
    • Browsers: Supported in Chrome 98+, Firefox 94+, Edge 98+ (no IE support).
    • Node.js: Supported in v17.0.0+ (LTS versions like v18+ include it).
  • Unsupported Types: Fails for Function, Symbol (as keys), Error, DOM nodes, and class instances (clones as plain objects).

Check Support#

Use caniuse.com to verify browser support. For older environments, polyfill with Lodash or JSON.parse(JSON.stringify()).

Method 5: Spread Operator with Recursion (Hybrid Approach)#

How It Works#

A middle ground between the spread operator (for shallow cloning) and recursion. Use the spread operator for top-level properties and recursion for nested objects/arrays.

Example#

function deepCloneSpread(obj) {
  if (typeof obj !== "object" || obj === null) return obj;
 
  // Handle arrays
  if (Array.isArray(obj)) {
    return obj.map(item => deepCloneSpread(item));
  }
 
  // Handle plain objects (use spread for top-level)
  const clonedObj = { ...obj };
  // Recurse on nested properties
  for (const key in clonedObj) {
    if (typeof clonedObj[key] === "object" && clonedObj[key] !== null) {
      clonedObj[key] = deepCloneSpread(clonedObj[key]);
    }
  }
  return clonedObj;
}
 
// Usage
const original = { a: 1, b: { c: 2 }, d: [3, 4] };
const clone = deepCloneSpread(original);
clone.b.c = 5;
console.log(original.b.c); // Output: 2

Pros#

  • Conciseness: Combines spread syntax with recursion for readability.
  • Works for Simple Cases: Good for objects with nested arrays/objects.

Cons#

  • Limited Type Support: Fails for Date, RegExp, Map, etc. (same as manual recursion without type checks).
  • Redundancy: Similar to the manual recursive method but less flexible.

Comparative Analysis: Which Method is Most Efficient?#

To choose the best method, evaluate based on:

CriteriaJSON.parse(JSON.stringify())Manual RecursiveLodash _.cloneDeepstructuredClone
Ease of Use⭐⭐⭐⭐⭐ (Simplest)⭐⭐ (Complex)⭐⭐⭐⭐ (1 line)⭐⭐⭐⭐ (1 line)
Data Type Support⭐ (Limited)⭐⭐⭐ (Customizable)⭐⭐⭐⭐⭐ (Extensive)⭐⭐⭐⭐ (Broad)
Circular References❌ (Throws error)⭐ (If added)⭐⭐⭐⭐⭐ (Handles)⭐⭐⭐⭐⭐ (Handles)
Performance⭐⭐ (Slow for large objects)⭐⭐⭐ (Depends on code)⭐⭐⭐⭐ (Optimized)⭐⭐⭐⭐⭐ (Fastest)
Bundle Size⭐⭐⭐⭐⭐ (0 KB)⭐⭐⭐⭐⭐ (0 KB)⭐⭐ (≈10KB minified)⭐⭐⭐⭐⭐ (0 KB)
Browser Support⭐⭐⭐⭐⭐ (All)⭐⭐⭐⭐⭐ (All)⭐⭐⭐⭐⭐ (All)⭐⭐⭐ (Modern)

Recommendations#

  • For Simple Objects (No Dates/Functions): Use JSON.parse(JSON.stringify()) for brevity.
  • For Custom Type Support: Use a manual recursive function (if you need full control).
  • For Production/Complex Apps: Use Lodash’s _.cloneDeep (battle-tested, handles edge cases).
  • For Modern Environments: Use structuredClone() (native, fast, and no dependencies).

Conclusion#

Deep cloning in JavaScript requires balancing simplicity, performance, and type support. For most projects:

  • Use structuredClone() if your environment supports it (modern browsers/Node.js 17+).
  • Use Lodash’s _.cloneDeep for broad compatibility and robust type handling.
  • Avoid JSON.parse(JSON.stringify()) for objects with dates, functions, or circular references.

Always test with your specific data structure to ensure the method meets your needs!

References#