Mastering JavaScript Object Copying: Shallow vs Deep Techniques

Understanding how JavaScript handles object references is crucial, as improper copying can cause unexpected side effects; this guide explains the differences between shallow and deep copies, demonstrates various methods—including Object.assign, spread operator, JSON serialization, lodash, and the native structuredClone API—and offers practical tips for choosing the right strategy.

JavaScript
JavaScript
JavaScript
Mastering JavaScript Object Copying: Shallow vs Deep Techniques

In JavaScript development, copying objects is a common but error‑prone operation. Because objects are passed by reference, inappropriate copying can cause unexpected side effects, such as modifying a copied object unintentionally affecting the original. Understanding deep vs shallow copy and mastering various copying techniques is essential for writing reliable, robust code.

Reference Type Characteristics

Before exploring copy methods, we need to understand the difference between primitive and reference types in JavaScript:

Primitive types (number, string, boolean): stored and passed by value

Reference types (object, array, function): stored and passed by reference

When you assign an object to another variable, you actually copy the reference, not the object’s content.

const original = { name: "John" };
const copy = original;

copy.name = "Jane";
console.log(original.name); // Output: "Jane"

This is why we need different copy strategies.

Shallow Copy

A shallow copy creates a new object but only copies the first‑level property values. Primitive values are copied, while reference values retain their original references.

Shallow copy implementations

Object.assign()

const original = { name: "John", details: { age: 30 } };
const shallowCopy = Object.assign({}, original);

shallowCopy.name = "Jane"; // Does not affect original
shallowCopy.details.age = 25; // Affects original!

console.log(original.name); // Output: "John"
console.log(original.details.age); // Output: 25

Spread Operator

const original = { name: "John", details: { age: 30 } };
const shallowCopy = { ...original };
// Behavior identical to Object.assign()

Array shallow copy methods

// Using slice()
const originalArray = [1, 2, { value: 3 }];
const slicedArray = originalArray.slice();

// Using spread operator
const spreadArray = [...originalArray];

// Using Array.from()
const fromArray = Array.from(originalArray);

// All these create shallow copies
slicedArray[2].value = 100;
console.log(originalArray[2].value); // Output: 100

Shallow copy pitfalls

The main issue with shallow copies is that modifying nested objects or arrays in the copy also modifies the original, because the nested references are shared.

Deep Copy

A deep copy creates a new object and recursively copies all nested objects, ensuring the copy is completely independent of the original.

Deep copy implementations

JSON serialization / deserialization

The simplest (but limited) deep‑copy method:

const original = { name: "John", details: { age: 30 } };
const deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.details.age = 25;
console.log(original.details.age); // Output: 30, original unchanged

Limitations:

Cannot copy functions, undefined, Symbol, BigInt

Cannot handle circular references

Loses prototype chain

Cannot correctly handle Date, RegExp, Map, Set, and other special objects

Recursive implementation (custom function)

This simple implementation works for most scenarios, but production code may need a more robust version to handle circular references and special object types.

Use libraries

In production, it is common to rely on well‑tested libraries for deep copying:

lodash _.cloneDeep() rfdc (Really Fast Deep Clone)

structuredClone() (native API)

Structured Clone Algorithm (structuredClone)

structuredClone()

is a relatively new global method that implements the structured clone algorithm, allowing deep copies of most built‑in JavaScript types.

Advantages:

Native API, no external dependencies

Handles most JavaScript built‑in types

Supports circular references

Generally good performance

Limitations:

Cannot clone functions

Cannot clone DOM nodes

Does not preserve prototype chain

Performance Considerations

Deep copying usually consumes more resources than shallow copying, especially for large, complex data structures. When choosing a copy strategy, consider:

Size and complexity of the data structure

Performance requirements

How the object will be used (whether a fully independent copy is needed)

Practical Tips

1. Mixed copy strategies

Sometimes you only need to deep‑copy specific nested properties:

2. Immutable data patterns

Adopt immutable data patterns instead of mutating objects directly:

3. Use Object.freeze() to prevent modification

Note: Object.freeze() only freezes the first‑level properties of an object.

Common Pitfalls and Solutions

Pitfall 1: Unexpected side effects

Solution: Use deep copy or explicitly copy the nested structures you need to modify.

Pitfall 2: Over‑deep copying

Solution: Only deep‑copy when necessary, or deep‑copy only the parts that require modification.

Pitfall 3: Special object types

const original = {
  date: new Date(),
  regex: /pattern/,
  func: function() { return true; }
};
// JSON method loses or mis‑converts these special types
const copy = JSON.parse(JSON.stringify(original));
console.log(copy.date); // String, not Date object
console.log(copy.regex); // {}
console.log(copy.func); // undefined

Solution: Use a dedicated deep‑copy library or a custom function that handles these special types.

Best Practices

Clarify the need: Determine whether you truly need a deep copy; often a shallow or partial deep copy suffices.

Choose the right tool:

Shallow copy: Object.assign() or spread operator

Simple deep copy: structuredClone() or JSON.parse(JSON.stringify()) Complex deep copy: lodash _.cloneDeep() or a custom recursive function

Test edge cases, especially when dealing with special object types or circular references.

Consider immutable data patterns to reduce the need for deep copies.

Balance performance: Find a trade‑off between deep copying and performance, particularly for large data structures.

performanceJavaScriptObject Copyshallow copystructuredClone
JavaScript
Written by

JavaScript

Provides JavaScript enthusiasts with tutorials and experience sharing on web front‑end technologies, including JavaScript, Node.js, Deno, Vue.js, React, Angular, HTML5, CSS3, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.