When Is the Difference Between Shallow and Deep Copy in JavaScript?

November 1, 2024

☕️ Support Us
Your support will help us to continue to provide quality content.👉 Buy Me a Coffee

When working with JavaScript, developers often encounter two distinct ways of copying non-primitive data types such as objects and arrays: shallow copy and deep copy. These concepts frequently appear in technical interviews at leading technology companies.

Understanding their differences and implementation methods is crucial for writing robust JavaScript applications. In this post, we will walk through the comparison and show you how to implement the two different copy methods with code examples.

Understanding Shallow Copy vs. Deep Copy

A shallow copy creates a new object while maintaining references to the nested objects from the original data structure. Think of it as copying the first floor of a building but keeping all the elevator connections to other floors exactly the same.

In contrast, a deep copy creates an entirely new object with completely independent copies of all nested objects. Using our building analogy, this would be like constructing an entirely new building with identical layouts but completely separate elevator systems.

To illustrate this difference, let's examine how the popular JavaScript utility library Lodash handles these two types of copying:

// Shallow copy example using Lodash's clone method
var employeeData = [{ name: "John" }, { name: "Jane" }];
var shallowCopy = _.clone(employeeData);
console.log(employeeData === shallowCopy); // false
console.log(shallowCopy[0] === employeeData[0]); // true - still sharing references

// Deep copy example using Lodash's cloneDeep method
var employeeData = [{ name: "John" }, { name: "Jane" }];
var deepCopy = _.cloneDeep(employeeData);
console.log(employeeData === deepCopy); // false
console.log(deepCopy[0] === employeeData[0]); // false - completely independent

Implementing Shallow Copy

Method 1: Manual Property Assignment

let originalEmployee = {
  id: 1,
  details: { department: "Sales" },
};
let copiedEmployee = {
  id: originalEmployee.id,
  details: originalEmployee.details,
};
console.log(originalEmployee === copiedEmployee); // false
console.log(originalEmployee.details === copiedEmployee.details); // true - shared reference

Method 2: Using Spread Operator

let originalEmployee = {
  id: 1,
  details: { department: "Sales" },
};
let copiedEmployee = { ...originalEmployee };
console.log(originalEmployee === copiedEmployee); // false
console.log(originalEmployee.details === copiedEmployee.details); // true - shared reference

Method 3: Using Object.assign

let originalEmployee = {
  id: 1,
  details: { department: "Sales" },
};
let copiedEmployee = Object.assign({}, originalEmployee);
console.log(originalEmployee === copiedEmployee); // false
console.log(originalEmployee.details === copiedEmployee.details); // true - shared reference

Implementing Deep Copy

Method 1: Using JSON Parse/Stringify

This method works well for basic data structures but has limitations with functions and special objects:

let originalEmployee = {
  id: 1,
  details: { department: "Sales" },
};
function deepCopy(item) {
  return JSON.parse(JSON.stringify(item));
}
let copiedEmployee = deepCopy(originalEmployee);
console.log(originalEmployee === copiedEmployee); // false
console.log(originalEmployee.details === copiedEmployee.details); // false - independent copy

Method 2: Using structuredClone

A modern built-in JavaScript method for deep copying:

let originalEmployee = {
  id: 1,
  details: { department: "Sales" },
};
let copiedEmployee = structuredClone(originalEmployee);
console.log(originalEmployee === copiedEmployee); // false
console.log(originalEmployee.details === copiedEmployee.details); // false - independent copy

Method 3: Custom Recursive Deep Clone

A comprehensive solution that handles various edge cases:

function deepClone(obj, cache = new WeakMap()) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }
  if (obj === null || typeof obj !== "object" || typeof value === "function") {
    return obj;
  }
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  const result = Array.isArray(obj)
    ? []
    : Object.create(Object.getPrototypeOf(obj));

  cache.set(obj, result);

  for (const key of Reflect.ownKeys(obj)) {
    const value = obj[key];
    result[key] = deepClone(value, cache);
  }
  return result;
}
☕️ Support Us
Your support will help us to continue to provide quality content.👉 Buy Me a Coffee