DEV Community

Cover image for Javascript Shallow Copy - what is a Shallow Copy?
Johnny Simpson
Johnny Simpson

Posted on • Edited on • Originally published at fjolt.com

Javascript Shallow Copy - what is a Shallow Copy?

Shallow copy and deep copy are terms thrown around in Javascript that can be confusing if you have never heard them before. It is quite common to hear that array methods like slice or filter make a shallow copy of the original array.

What is a shallow copy in JavaScript?

A shallow copy of an arrays or object is one where they both have the same reference in memory. That means that if you change the shallow copy, it may change the original copy too. I say may, since that is not always the case.

Let's look at an example using slice:

let arrayOne = [ '⚡️', '🔎', '🔑', '🔩' ];
let arrayOneSlice = arrayOne.slice(2, 3);  

console.log(arrayOne); // [ '⚡️', '🔎', '🔑', '🔩' ]
console.log(arrayOneSlice); // [ '🔑' ]
Enter fullscreen mode Exit fullscreen mode

Here we have an array, which we then slice in the variable arrayOneSlice. Both of these arrays have the same reference in memory, since slice makes a shallow copy of them. So if we try to update arrayOneSlice, it will affect arrayOne too, right?

let arrayOne = [ '⚡️', '🔎', '🔑', '🔩' ];
let arrayOneSlice = arrayOne.slice(2, 3);  

// Update arrayOneSlice
arrayOneSlice[2] = '⚡️'

console.log(arrayOne); // [ '⚡️', '🔎', '🔑', '🔩' ]
console.log(arrayOneSlice); // [ '🔑', empty, '⚡️' ]
Enter fullscreen mode Exit fullscreen mode

Only, it doesn't - since we used the square bracket notation, Javascript interprets this as putting a new value into the [2] position. So only arrayOneSlice is updated - and for good reason too. Although '🔑' is at position [2] in arrayOne, it is in position [0] in arrayOneSlice. This can give the illusion that these two arrays are copies and act independently of each other - but that's not the case either. Consider the following example:

let arrayOne = [ { items: [ '🔎' ]}, '🔎', '🔑', '🔩' ];
let arrayOneSlice = arrayOne.slice(0, 3);  

// Update arrayOneSlice
arrayOneSlice[0].items = [ '⚡️' ]

console.log(arrayOne); // [ { items: [ '⚡️' ]}, '🔎', '🔑', '🔩' ]
console.log(arrayOneSlice); // [ { items: [ '⚡️' ]}, '🔎', '🔑' ]
Enter fullscreen mode Exit fullscreen mode

Here, we updated arrayOneSlice[0].items, and its updated on both arrays, since items exists on both arrays at the same position, and we didnt assign a new value, but rather used the dot . notation to update an existing property. In Javascript, this updates both the original and the copy we made using slice.

The main point to remember with shallow copies, is that adjusting one can affect the original you are trying to copy - the reference in memory is the same, and the reference points to the values of the array - so you have to be more careful. What you don't want to do is create unexpected behaviour where the original and copy of an array do not update in sync when you expected them to.

So how do you make deep copies in Javascript?

Javascript has historically had a bit of a problem with deep copies. Most methods in Javascript like the three dots or spread syntax, Object.create(), Object.assign(), and Array.from() all make shallow copies.

Deep copies have different references in memory, though, so you don't have to worry about mutating the original when using them. This makes them very useful when we want to avoid that.

Deep copies can be made via serialisation, or a custom script to copy each part of an object or an array into a new one - creating a new reference in memory. For example, this will create a new array in Javascript with a new reference:

let myArray = [ 1, 2, 3, 4 ];
let deepCopy = JSON.parse(JSON.stringify(myArray));
Enter fullscreen mode Exit fullscreen mode

This is not necessarily the best way to do it though. You can also use the structuredClone() function to make a deep copy:

let myArray = [ 1, 2, 3, 4 ];
let deepCopy = structuredClone(myArray);
Enter fullscreen mode Exit fullscreen mode

Now we have created new arrays with deep copies, we no longer need to worry about messing up the original when we change the copy.

Conclusion

Shallow copies are quite confusing, and one of the many quirks which Javascript presents. Understanding what they are can save you a lot of headaches when debugging in the future, and using deep copies is a good way to avoid some of these issues.

Top comments (14)

Collapse
 
caioragazzi profile image
Caio Ragazzi

Thanks for sharing! I didn't know the structuredClone() method... never used it before!

Thanks

Collapse
 
3ankur profile image
Ankur V

Thank you for sharing :)

Collapse
 
llorx profile image
Jorge Fuentes • Edited

"This can give the illusion that these two arrays are copies and act independently of each other - but that's not the case either."

This is a false claim. They are copies. When you slice, the array is copied to another section (the values and the pointer references). Each array has its own section of memory that do not overlap with the other section.

What you are talking about are pointer references to objects, which are also stored in another section of the memory. When you slice, those pointer references are copied, so the element 0 in both arrays point to the same object, but that pointer in the element 0 is stored in different parts of the memory, hence copied. When you do arr[0].X, the .X means to follow the pointer to the object and access the property X, hence both arrays will access the same object as each array has the same pointer reference in the position 0, but you can modify that pointer reference in one array with arr[0] = obj2 and it will not affect the other array because the position 0 is stored in different parts of the memory. Now each array will point to a different object in the position 0 because you modified the memory that stores the pointer in the position 0 only in one of the arrays.

Collapse
 
smpnjn profile image
Johnny Simpson

correct yes, i am referring to them being both copies and acting independently as being false. being copies alone is true.

Collapse
 
methe1 profile image
mohammed ezzitouni

Right on! slice's doing a deep copy, cuz a shallow copy would be pointers pointing to the start, and the end indices are given to the slice function, that way you have direct access to the array where the beginning and the end of it are different than the original start (0) & end (length -1).

Collapse
 
hudson3384 profile image
Hudson Arruda

The ES6 version add the spread function :
Let arr = [1,2,3]
Let arr2 = [...arr]
Arr.push(4)
Console.log(arr) // 1,2,3,4
Console.log(arr2) // 1,2,3

Collapse
 
smpnjn profile image
Johnny Simpson

the spread syntax still only makes a shallow copy.

Collapse
 
chadgauth profile image
Chad Gauthier • Edited

It's a deep copy if the data is not nested, if it is nested those are shallow:

const arr = [1,2,[1,2]]
const arr2 = [...arr]
arr[2].push(3)
console.log(arr2) // 1,2,[1,2,3]

Thread Thread
 
smpnjn profile image
Johnny Simpson

no spread syntax creates a shallow copy of data with the same ref in memory. That's the main differentiator between a deep and shallow copy - their ref in memory. In fact pretty much all array methods create shallow copies.

Collapse
 
fyodorio profile image
Fyodor

FWIW the way of using JSON methods is not the best one, you might loose some data in cloned objects

Collapse
 
smpnjn profile image
Johnny Simpson

Correct, I've added that note.

Collapse
 
davidyaonz profile image
David Yao

Sometimes bugs were found when implementing spread operator, it happens to me as storms all the time.

Collapse
 
azzarox profile image
Azzarox

Basically, if you create shallow copy and use mutating function or smth on it, it should change the original as well?

Collapse
 
smpnjn profile image
Johnny Simpson

In ideal circumstances, yes - unless you do something like the example above where you slice the array and then update using the square bracket notation.