Normal Copy vs Shallow Copy vs Deep Copy
Tags: webdev, javascript, programming, tutorial
Published: 2023-06-02 19:00:00

## What is Copy?

Copy is a process of creating a new object in memory and storing the same values as the original object. There are three types of copy:

1. [Normal Copy](#normal-copy)
2. [Shallow Copy](#shallow-copy)
3. [Deep Copy](#deep-copy)

Let's look at each of them in detail.

Note: For Primitive values normal copy is OK, but it behaves unexpectedly differently in non-primitive type. Why is that? We will see that below. So, keep in mind that we are talking about objects, arrays, but not primitive values.

## Normal Copy

A normal copy of an object is a copy that creates a new object in memory and shares the same reference as the original object. So, if we change the value of any property of the copied object, then the original object will also be affected as both the objects are referencing the same values in memory, as you can see in the below example.

```js 
const original = { a: 1, b: 2, c: 3 };
const copy = original;

original.a = 10;
console.log(copy.a); // 10

copy.b = 20;
console.log(original.b); // 20
```
![image](https://github.com/mrizwanashiq/mrizwanashiq/assets/27571455/af7c1d1e-39c8-4d0c-85ab-717b9b6cfe89)

So, in short, a normal copy is not a good idea to make a copy of an object. Because, if we change the value of any property of the new object, then the original object will also be affected, as both objects are referencing the same values in memory.

What's the solution? 🤔

We can use [shallow copy](#shallow-copy) or [deep copy](#deep-copy) to make a copy of an object depending on the object structure.

Let's look at each of them in detail.

## Shallow Copy

It copies the values of the original object to the new object. To make shallow copy, we can use the spread operator `...` to copy the values of the original object to the new object. Check the below example:

```js
const original = { a: 1, b: 2, c: 3 }; // Object with 3 properties, no nested objects
const copy = { ...original }; // ... is the spread operator, which copies the values of the original object to the new object

copy.a = 10; // Change the value of the property of the new object to 10
console.log(original.a); // 1 (not affected)
```

![image](https://github.com/mrizwanashiq/mrizwanashiq/assets/27571455/161eee3e-3f8e-4184-b084-4962c247f8e4)

See, in the above example, we have changed the value of the `a` property of the new object, but the value of the `a` property of the original object is not affected. This is because, in shallow copy, we are only copying the original object's values to the new object, not the reference of the original object. So, if we change the value of any property of the new object, then the original object will not be affected.

But if the object has a nested object (which means have another object against any key), then it will copy the reference of the nested object of the original object, not the values. Check the below example:

```js
const original = {
	a: 1,
	b: 2,
	c: 3,
	d: {
		e: 4,
		f: 5,
	},
}; // Object with 3 properties, 1 nested object with 2 properties

const copy = { ...original };

copy.d.e = 10; // Change the value of the e property of the nested object
console.log(original.d.e); // 10 (affected)
```

![image](https://github.com/mrizwanashiq/mrizwanashiq/assets/27571455/8d586195-961b-4829-9534-c0166285bc4d)

So, if the object has a nested object, then shallow copy has the same problem as normal copy. If we change the value of any property of the new object, then the original object will also be affected, as both objects are referencing the same values in memory.

So, what's the solution? We can use [deep copy](#deep-copy) to make a copy of an object.

## Deep Copy

Deep Copy creates a new object in memory and stores the same values as the original object. There are multiple ways to create a deep copy of an object or array, but we will look at the most common ways.

- [What is Copy?](#what-is-copy)
- [Normal Copy](#normal-copy)
- [Shallow Copy](#shallow-copy)
- [Deep Copy](#deep-copy)
  - [`JSON.parse(JSON.stringify(obj))`](#jsonparsejsonstringifyobj)
  - [`structuredClone(obj)`](#structuredcloneobj)
- [What to use when?](#what-to-use-when)
- [Conclusion](#conclusion)

### `JSON.parse(JSON.stringify(obj))`

`JSON.stringify()` method converts a JavaScript object (or array) to a JSON string, and `JSON.parse()` method parses a JSON string and constructing the JavaScript value or object described by the string.

```js
const original = { a: 1, b: 2, c: 3 }; // Object with 3 properties, no nested objects
const copy = JSON.parse(JSON.stringify(original)); // Deep copy

copy.a = 10; // Change the value of the a property of the new object
console.log(original.a); // 1 => The value of the a property of the original object is not affected
```

But, it'll copy the values of the original object to the new object (would not share the same reference), and if we change the value of any property of the new object, then the original object will not be affected, and this also applies to nested object.

But this method has a problem:

1. If the object has a function, then it will not be copied, as you can see in the below example:

   ```js
   const obj = {
   	a: 1,
   	b: 2,
   	c: 3,
   	d: {
   		e: 4,
   		f: 5,
   	},
   	g() {
   		console.log("Hello");
   	},
   }; // Object with 3 properties, 1 nested object with 2 properties, 1 function

   const copy = JSON.parse(JSON.stringify(obj));

   console.log(copy); // { a: 1, b: 2, c: 3, d: { e: 4, f: 5 } }
   ```

2. If the object has a transferable object like `Date`, then it will be converted to a string while copying.

   ```js
   const obj = {
   	a: 1,
   	b: 2,
   	c: 3,
   	d: {
   		e: 4,
   		f: 5,
   	},
   	g: new Date(),
   }; // Object with 3 properties, 1 nested object with 2 properties, 1 Date object

   const copy = JSON.parse(JSON.stringify(obj));

   console.log(copy); // { a: 1, b: 2, c: 3, d: { e: 4, f: 5 }, g: "2021-09-01T06:30:00.000Z" }
   ```

3. If the object has a circular reference, then it will throw an error.

   ```js
   const obj = {
   	a: 1,
   	b: 2,
   	c: 3,
   	d: {
   		e: 4,
   		f: 5,
   	},
   }; // Object with 3 properties, 1 nested object with 2 properties

   obj.d.g = obj; // Circular reference

   const copy = JSON.parse(JSON.stringify(obj));

   // This will error
   console.log(copy); // Uncaught TypeError: Converting circular structure to JSON
   ```

So, what's the solution? We can use [structuredClone](#structuredcloneobj) to make a copy of an object.

### `structuredClone(obj)`

It is a deep copy function that can copy any object, including nested objects, arrays, functions, and even transferable objects, such as `Date`, `Set`, `Map`, etc. It is built into the JavaScript runtime and is available in all modern browsers, so don't even need to install any new package.

```js
const person = {
	name: "Rizwan",
	age: 25,
	address: {
		city: "Rahim Yar Khan",
		country: "Pakistan",
	},
	date: new Date(123),
	education: [
		{
			degree: "BSIT",
			university: "KFUEIT",
		},
	],
};

const copied = structuredClone(calendarEvent);
```

In the above example, we not only copy the object, but also the nested array, and even the Date object, and all work precisely as expected:

- `copied.education` // [{ degree: "BSIT", university: "KFUEIT" }]
- `copied.date` // Date: Wed Dec 31 1969 16:00:00
- `person.education === copied.education` // false, means both do not share the same reference

That's right, `structuredClone` can not only do the above but additionally:

- Clone infinitely nested objects and arrays
- Clone circular references
- Clone any transferable objects, such as `Date`, `Set`, `Map`, `Error`, `RegExp`, `ArrayBuffer`, `Blob`, `File`, `ImageData`, and several more

So for example, this madness would even work as expected:

```js
const kitchenSink = {
	set: new Set([1, 3, 3]),
	map: new Map([[1, 2]]),
	regex: /foo/,
	deep: { array: [new File(someBlobData, "file.txt")] },
	error: new Error("Hello!"),
};
kitchenSink.circular = kitchenSink;

// ✅ All good, fully and deeply copied!
const clonedSink = structuredClone(kitchenSink);
```

Note: `JSON.parse(JSON.stringify(obj))` is surprisingly faster than `structuredClone(obj)`

## What to use when?

If you want to copy an object that has no nested objects, then you can use shallow copy using the spread operator (`...`). But if the object has nested objects, then you should use the deep copy.

As in deep copy, we have multiple options, if the object does not have functions, and transferable objects like `Date`, etc., then `JSON.parse(JSON.stringify(obj))` is best, otherwise `structuredClone()` is the method you can use.

If you want to copy the reference, then the normal copy is the thing.

## Conclusion

In this article, we learned about the difference between Normal Copy, Shallow Copy, and Deep Copy. We also learned how to create shallow and deep copies of objects.