When Is the Difference Between Shallow and Deep Copy in JavaScript?
November 1, 2024
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;
}