[JS Basics] Distinguishing Shallow Copy and Deep Copy in JavaScript

When working with JavaScript, especially with objects and arrays, you’ll soon encounter a crucial but often confusing concept: data copying. Accidentally modifying an object without realizing it can lead to hard-to-find bugs that take hours to fix.

The main culprit behind these issues is the difference between Shallow Copy and Deep Copy.

Distinguishing Shallow Copy and Deep Copy in JavaScript

This article will help you master these two concepts, so you can write cleaner, more predictable code and avoid those “out of nowhere” bugs. Let’s get started! 🚀

Core Foundation: Primitive vs Reference Types

Before diving into shallow and deep copy, let’s understand how JavaScript handles data types.

1. Primitive Types

Primitive types like string, number, boolean, null, undefined, symbol, and bigint store their actual values directly.

When you assign a primitive variable to another, you’re copying the value. They are completely independent.

let a = 100
let b = a // Copy value of a to b

console.log(b) // 100

// Changing b does not affect a
b = 200
console.log(a) // 100 (Unchanged)
console.log(b) // 200

2. Reference Types

Types like Object, Array, and Function do not store values directly. Instead, the variable holds a reference or address pointing to the object’s location in memory.

💡 Analogy: Imagine a variable as a piece of paper.

  • With primitive, the paper has the value written on it ("100").
  • With reference, the paper has the address of a house (0x123...), and the actual data is inside that house.

When you assign a reference variable to another, you’re just copying the address, not building a new house. Both variables now point to the same house.

let user1 = { name: 'Alice', age: 25 }
let user2 = user1 // Copy memory address of user1 to user2

// Change data via user2
user2.name = 'Bob'

// user1 is also changed!
console.log(user1.name) // "Bob" 😱

This is the root of many issues and why we need copying techniques.

Shallow Copy: Only Copies the Surface 🌊

A shallow copy creates a new object or array, then copies properties from the original to the new one.

  • If a property is a primitive, its value is copied.
  • If a property is a reference (an object or array inside), only the reference (address) is copied, not the actual nested object.

In other words, the new and original objects share nested objects/arrays.

Ways to Perform Shallow Copy

1. Spread Syntax (...)

This is the most popular and modern way for both objects and arrays.

const original = {
  name: 'Laptop',
  price: 1000,
  details: {
    // This is a nested object
    brand: 'Apple',
    year: 2023,
  },
}

const copy = { ...original }

// Change top-level property (primitive)
copy.price = 1200
console.log(original.price) // 1000 (Not affected ✅)

// Change nested object property
copy.details.brand = 'Dell'
console.log(original.details.brand) // "Dell" (Affected 😱)

As you can see, original and copy share the details object. Changing one affects the other.

2. Object.assign()

Works similarly to Spread Syntax for objects.

const original = { name: 'Alice', address: { city: 'Hanoi' } }
const copy = Object.assign({}, original)

copy.address.city = 'Da Nang'
console.log(original.address.city) // "Da Nang" (Affected 😱)

3. Array.prototype.slice() and Array.from()

Common methods for shallow copying arrays.

const originalArray = [1, 2, [3, 4]]
const copiedArray = originalArray.slice()

// Change nested array
copiedArray[2][0] = 99
console.log(originalArray[2][0]) // 99 (Affected 😱)

When to use Shallow Copy? When your object/array is flat (no nested elements) or when you intentionally want to share nested objects to save memory or synchronize data.

Deep Copy: Creating a Fully Independent Copy 🧱

A deep copy creates a new object/array and recursively copies all data. Every nested object and array is also copied, creating completely new instances.

The result: two objects/arrays are fully independent, with no shared references. Changes to the copy never affect the original.

Ways to Perform Deep Copy

1. The JSON Trick: JSON.stringify() and JSON.parse()

This is the simplest and fastest way for objects containing only valid JSON data (string, number, boolean, array, object).

const original = {
  name: 'Laptop',
  price: 1000,
  details: {
    brand: 'Apple',
    year: 2023,
  },
  buy: function () {
    console.log('Bought!')
  },
}

const deepCopy = JSON.parse(JSON.stringify(original))

// Change nested object property
deepCopy.details.brand = 'Dell'

console.log(original.details.brand) // "Apple" (Not affected ✅ Great!)

⚠️ Important caveats of the JSON trick:

  • Data loss: Properties with values of function, undefined, or Symbol are ignored.
  • Incorrect data: Date objects become strings. NaN, Infinity become null.
  • Cannot handle circular references.

Only use this method when you’re sure your object is simple and JSON-compatible.

2. The structuredClone() API (Modern Approach)

A new built-in API in browsers and Node.js, designed specifically for deep copying. Much more powerful than the JSON trick.

const original = {
  name: 'Complex Object',
  date: new Date(),
  nested: { set: new Set([1, 2, 3]) },
}

const deepCopy = structuredClone(original)

deepCopy.nested.set.add(4)

console.log(original.nested.set) // Set(3) { 1, 2, 3 } (Not affected ✅)
console.log(deepCopy.nested.set) // Set(4) { 1, 2, 3, 4 }

structuredClone() can handle more complex types like Date, RegExp, Set, Map, ArrayBuffer, etc. However, it cannot copy functions and will throw an error for circular references.

3. Using External Libraries (Most Reliable)

For complex applications, the safest and most reliable way is to use well-tested functions from popular libraries like Lodash.

Lodash’s cloneDeep can handle almost any case, including functions, complex types, and circular references.

// Install lodash: npm install lodash
import _ from 'lodash'

const original = {
  name: 'Super Object',
  run: () => console.log('Running!'),
  details: { brand: 'BrandX' },
}

const deepCopy = _.cloneDeep(original)

deepCopy.details.brand = 'BrandY'

console.log(original.details.brand) // "BrandX" (Not affected ✅)
console.log(typeof original.run) // "function"
console.log(typeof deepCopy.run) // "function" (Function is also copied ✅)

Conclusion: Choose the Right Tool for the Job

Understanding the difference between Shallow Copy and Deep Copy is not just theory—it’s an essential skill for writing robust, maintainable JavaScript code.

CriteriaShallow CopyDeep Copy
DefinitionCreates new object/array, copies top-level props.Creates new object/array, recursively copies all properties.
Nested objectsShares references with the original.Nested objects are copied as new, fully independent objects.
IndependencePartially independent (not for nested levels).Fully independent.
SpeedFaster.Slower due to traversing the whole structure.
Methods..., Object.assign, slice()structuredClone(), JSON.parse/stringify, _.cloneDeep() (Lodash)
When to useFlat data, no nesting. Need performance.Complex data. Need absolute independence.

Shallow Copy vs Deep Copy

  • Shallow Copy is fast and efficient for simple data structures. Use Spread (...) as your default choice.
  • Deep Copy is your savior when you need complete data independence and want to avoid unwanted side effects. structuredClone() is a modern, powerful choice, while Lodash’s _.cloneDeep() is the most comprehensive solution for complex cases.

Hopefully, this article has given you a clear and deep understanding of these two important concepts. Now you can be more confident in managing and manipulating data in your projects!

Related Posts

[JS Basics] ES6+ Features: What You Need to Know to Write Modern JavaScript

This article is the most detailed handbook on JavaScript Events. Learn how to listen for and respond to user actions on your website, helping you build more interactive and smoother web applications.

[JS Basics] Functions in JavaScript: Concepts, Declarations, and Detailed Examples

Discover the power of Functions in JavaScript! Step-by-step guide on how to write and use functions effectively. Start learning now to level up your coding skills.

[JS Basics] Manipulating the DOM with JavaScript: A Detailed Guide for Beginners

DOM manipulation is a foundational skill for every web developer. This article provides practical examples and detailed explanations on how to interact with HTML and CSS using JavaScript.

[JS Basics] Demystifying the this Keyword in JavaScript: When and How to Use It?

The this keyword in JavaScript is one of the most important yet confusing concepts. This article will help you master this through practical examples and easy-to-understand explanations, even if you are a beginner.