Understanding DeepCopy vs shallow Copy with Javascript

Khyati Jha
5 min readNov 1, 2023

--

Have you ever had the experience of having an object or array value change but being unable to pinpoint the particular line of code where the change occurred?
It happened to me when I was new to Javascript, and I couldn’t figure out why my object was changing despite the fact that I hadn’t directly updated the value. I couldn’t figure it out even after a lot of debugging. Then I recalled hearing the terms shallow and deep copy. And yes, I had found my answer.
I have also seen that not understanding the concept of shallow and deep copy can lead to lot of bugs and wastage of time to debug.

Lets try to understand in detail but with some exercises. Feel free to execute each code snippet on your own.

Can you guess the output of following code snippet: (The answer is below but don’t cheat and try to answer yourself.)

let a = 2
let b = a
b = 4
console.log("A: ", a);
console.log("B: ", b);

I hope you got it right.
The output will be:

A: 2
B: 4

Lets try one more. Try to answer the output of following:

let a = [1,2,3]
let b = a
b.push(4)
console.log("A: ", a);
console.log("B:", b);

some might think the output will be :
A: [1,2,3]
B: [1,2,3,4]

But thats not the case. The output is:

A: [1,2,3,4]
B: [1,2,3,4]

But we never updated valriable a. So what just happened! Lets try to understand.

Primitive and Non-Primitive Data Types:

In Javascript we have 2 types of data.
1. Primitive
2. Non Primitive

Primitive data types include Number, string, boolean, undefiend, null, symbol and BigInt.
and Non-primitive data types includes object and arrays. Arrays are also special type of object.

Whenever we assign a primitive variable to another varible, there is a new copy is created which is independent of the original value.

But when a non-primitive variable is assigned to another variable, instead of having a new value, the new variable starts pointing to the same variable. Or we can say now they both share the same reference. Its like calling the same person with two different names.

Thats why in second snippet, when we append 4 to b, a also gets affected as they share the same reference.

So now you know, = can not be used to crate a copy of array/object then how do we do it?

Well there are few ways for that and most common one that came with ES6 is spread operator.

const a = [1,2,3]
const b = [...a]
b.push(4)
console.log("A: ", a); //A: [ 1, 2, 3 ]
console.log("B:", b ); // B: [ 1, 2, 3, 4 ]

Okay that works. Great!

Now lets try to answer one more snippet:

const classroom = {
students: [
{ name: "Naveen", score: 60 },
{ name: "Sam", score: 49 },
{ name: "John", score: 89 },
{ name: "Ram", score: 70}
]
}

const getResults = (originalClassroom) => {
const copiedClassroom = { ...originalClassroom }

copiedClassroom.students.map(student=>{
if(student.status>=60){
student.status = "Pass"
}else{
student.status = "Fail"
}
})

return copiedClassroom
}

console.log("OriginalClassroom: ", classroom)
console.log("Results: ", getResults(classroom))

As per our understanding so far, the result should be following:

OriginalClassroom:  {
students: [
{ name: 'Naveen', score: 60 },
{ name: 'Sam', score: 49 },
{ name: 'John', score: 89 },
{ name: 'Ram', score: 70 }
]
}
Results: {
students: [
{ name: 'Naveen', score: 60, status: 'Fail' },
{ name: 'Sam', score: 49, status: 'Fail' },
{ name: 'John', score: 89, status: 'Fail' },
{ name: 'Ram', score: 70, status: 'Fail' }
]
}

But Result is following:

OriginalClassroom:  {
students: [
{ name: 'Naveen', score: 60, status: 'Fail' },
{ name: 'Sam', score: 49, status: 'Fail' },
{ name: 'John', score: 89, status: 'Fail' },
{ name: 'Ram', score: 70, status: 'Fail' }
]
}
Results: {
students: [
{ name: 'Naveen', score: 60, status: 'Fail' },
{ name: 'Sam', score: 49, status: 'Fail' },
{ name: 'John', score: 89, status: 'Fail' },
{ name: 'Ram', score: 70, status: 'Fail' }
]
}
Photo by jaikishan patel on Unsplash

This happened because spread operator only creates a shallow copy of the object.

“You can use spread syntax to make a shallow copy of an array. Each array element retains its identity without getting copied.”
source: Mdn web docs

Shallow Copy

when we create Shallow copy of an object, the underlying values of the object still share the same reference. which means, if values of any one variable changes, it reflects in another variable as well.
In simple words, the nested values of both the variables still point to the same values.

How to create a shallow copy:

In JavaScript, standard built-in object-copy operations (spread syntax, Array.prototype.concat(), Array.prototype.slice(), Array.from(), Object.assign(), and Object.create()) create shallow copies.

Deep Copy

when we create a deep copy, even the underlying values don’t share the reference. Hence a completely independent copy of the variable is created. And any changes in any of the variables doesnt affect the other variable.

How to Create a deep copy:

  1. Using JSON.parse() for serializable objects:
const classroom = {
students: [
{ name: "Naveen", score: 60 },
{ name: "Sam", score: 49 },
{ name: "John", score: 89 },
{ name: "Ram", score: 70}
]
}
const classroomDeepCopy = JSON.parse(JSON.stringify(classroom));

Now any change in classroomDeepCopy will not affect the classroom object.

2. HTML DOM API: structuredClone:

const classroom = {
students: [
{ name: "Naveen", score: 60 },
{ name: "Sam", score: 49 },
{ name: "John", score: 89 },
{ name: "Ram", score: 70}
]
}
const classroomDeepCopy = structuredClone(classroom);

3. Using external libraries:

i.e. loadash-cloneDeep

The purpose of this article was to introduce you to the concept of shallow and deep copy. Hope It was useful. Thanks for reading so far. Happy Learning!😄

--

--

Khyati Jha

Software Engineer👩‍💻 | Learning and Sharing 🦋