Everything you want to know about deep and shallow copy

Mayank Gupta
Sep 13 · 15 min read

Often, to create an exact copy of an object in JavaScript, we need to create a new Object from existing objects. There are various methods to achieve this. This article is focused on the different ways in which an object can be cloned/copied. But before we dig deep into the details, we need to first understand the concept of deep and shallow copying of objects.


Understanding Deep and Shallow Copy

Let’s look into the details of the shallow and deep copy. As the image above shows, a shallow copy has common data shared between the copy and the original, whereas the deep copy of an object, does not have any data shared.

In the object below, we have a few properties which represent value types (name, age, designation) and other properties that represent a complex object (address). Properties containing complex objects are referred to as “reference variable”

var userName = {
name: "Mayank Gupta",
age: 30,
designation: "Developer",
address: {
street: "Rohini",
city: "Delhi"
}
}

Shallow Copying Objects

An Object can be considered as Shallow Copied when a separate copy of top-level value type properties is assigned to the new Object and the properties that represent an object are copied by reference to the destination folder.

Summary for Shallow Copy:

  1. Destination object contains a separate copy of name, age, and designation.
  2. Reference of Complex Object (“address”) is copied to Destination Object

In the case of Shallow Copy, the separate copy of the reference variable is not created. The memory reference of the complex Object in Source Object is copied to the Destination Object.


Disadvantages of Shallow Copy

The problem with shallow copy is that, if the user makes changes to the complex object (update street property of address object) of the source object (userName), it is also reflected in the Destination Object, since it points to the same memory address.

Changes to the reference objects in source object are also reflected in the Destination Object.


Deep Copy of Object

The object is said to be deep copied when each property to the object points to separate copy, even if the property points to an object (reference values). A separate copy of the reference object is created for the destination object. In case of deep copy, source object reference properties and destination object reference properties are pointing to different memory locations.

A summary of deep copy:

  • The destination object contains a separate copy of name, age, and designation. Separate instances of value types.
  • Separate Reference for Complex Objects (“address”) is created

“Address” property of Source and destination object point to different memory locations. If the user updates the address property in the source object, the updates are not reflected in the destination object.


Different Ways to Copy Objects

There are multiple ways to copy an object, each with advantages and disadvantages:

  1. Using “for-of” loop to iterate through the object properties
  2. Using “JSON.parse” and “JSON.stringify”
  3. Working with “Object.assign” function
  4. Using “Object.create” to copy objects
  5. Using recursion for object copying

1. Copy Properties of Source using “for-in” loop

This is the easiest method to copy an object. This method involves iterating each property of the object and copy those key-value pairs to the destination object.

Let’s take a look at this approach with some sample code:

var employeeData = {
name: "Mayank",,
age: 31,
designation: "Developer"
}

var destinationObject = {};

for(property in employeeData) {
destinationObject[property] = employeeData[property];
}

console.dir(destinationObject);

Benefits of using this method:

  1. It’s one of the simplest ways to copy the properties
  2. Copies the user-defined methods to the destination object

User-defined functions inside the object can be copied using this approach, where a new function is copied to the destination object. Let’s see how that works:

var employeeData = {
name: "Mayank",
age: 31,
designation: "Developer",
getData: function() {
alert(this.name)
}
}

var destinationObject = {};

for(property in employeeData) {
destinationObject[property] = employeeData[property];
}

console.dir(destinationObject);

The Object employeeData contains a function. Using the for-in, we can iterate all the enumerable properties also involving function. The object created destinationObject, also contains this function implementation available to this object. The copied output is displayed below:

3. We can copy Prototype Functions and Properties

“for-of” can even iterate through the “prototype” properties and methods of an Object, therefore all prototype functions and properties can be copied to the destinationObject. Here’s an example:

function Employee() {
this.name = "Mayank";
this.age = 20;
}

Employee.prototype.showData = function() {
console.log("User Name: " + this.name)
}

var userDetails = new Employee();

var destinationObject = {}
for(key in userDetails) {
destinationObject[key] = userDetails[key]
}

console.dir(destinationObject)

Here, the employee constructor function contains a prototype function showDetails. We can iterate over all keys associated with the object, including the prototype methods and properties, using for-of. Since we have access to prototype properties and functions during iteration, we can copy these key-value pairs to the destination object. The output of the above execution is given below:

Disadvantages

It can only Iterate the “enumerable” properties of the object.

We can define properties as “non-enumerable”. If the property is specified as “non-enumerable”, we cannot iterate through that property using the “for-of” loop, so it won’t be copied to the destination object.

See the code below for an in-depth explanation:

var employeeData = {
name: "Mayank",
age: 31,
designation: "Developer",
getData: function() {
alert(this.name)
}
}

Object.defineProperty(employeeData, 'salary', {
value: 10000,
writable: false,
enumerable: true
});

var destinationObject = {};

for(property in employeeData) {
destinationObject[property] = employeeData[property];
}

console.dir(destinationObject);

Here we are adding a non-enumerable property to the employeeData object. In this case, the for-of loop cannot iterate through the non-iterable properties and hence, the property cannot be copied.

The output for the above execution is:

The salary property is not copied in this case, since its non-enumerable property of the source object.

Complex objects are copied by reference (no deep copy).

https://gist.github.com/Mayankgupta688/99238304741498c90ff933228b58da9c

The object properties are copied by reference. The code above contains the reference inside the property “address”. When we copy the address from employeeData to destinationObject, the reference of the object is copied and not the actual object.

So, if you make any changes to employeeData, it will be available to destinationObject also, since they are pointing to the same reference.

So in this case, a deep copy of objects is not created.


Other Ways to Iterate the Objects in JavaScript

There are other ways to iterate through the keys of an object in JavaScript.

  • Use Object.keys
  • Using Object. getOwnPropertyNames

These methods are similar to iterating with for-of. Let’s see the differences and the purpose of these functions and properties

Iterating with Object.keys

  1. Object.key does not iterate the prototype property
  2. Object.key cannot iterate through non-enumerable properties.

Iterating with Object. getOwnPropertyNames

  1. Object.getOwnPropertyNames does not iterate the prototype property
  2. It can iterate through non-enumerable properties.

This is the distinctive functionality of Object.getOwnPropertyName: it can also get non-enumerable properties.

See the code below:

var employeeData = {
name: "Mayank",
age: 31,
designation: "Developer",
address: {
state: "Delhi",
country: "India"
}
}

var destinationObject = {};

for(property in employeeData) {
destinationObject[property] = employeeData[property];
}

console.dir(destinationObject);

employeeData.address.state = "Haryana";

console.log("Source Object Data Updated....")

console.dir("Destination State Data: " + destinationObject.address.state);

Here salary is a non-enumerable property. getOwnPropertyNames can be used to retrieve any non-enumerable value, also along with the enumerable properties. The code of the above execution is:

We can use either of the iteration technique to iterate through the list of all the properties that exist inside an object. The properties available from the iterations can be used to copy the property from one object to another.


2. Use JSON Stringify and JSON Parse

In the following approach, we can convert the object to the JSON string using JSON.stringify. Once the Object has been stringified, it can be converted back to object using “JSON.parse” and saved into a separate object. This will help us achieve a deep copy of the specified object.

var employeeData = {
name: "Mayank",
age: 31,
designation: "Developer"
}

Object.defineProperty(employeeData, 'salary', {
value: 10000,
writable: false,
enumerable: false
});

console.dir(Object.getOwnPropertyNames(employeeData));

The output for the specified code is:

A new copy of the object is created. The object copied points to a different memory location. This method provides a deep copy of the specified object.

The advantages of this method are:

  1. It can create deep copy of the object:
var userInfo = {
name: "Mayank",
age: 31,
designation: "Developer",
address: {
streetNumber: 10,
city: "Delhi",
country: "India"
}
}

var stringifiedObject = JSON.stringify(userInfo);

var newDeepCopy = JSON.parse(stringifiedObject);

console.dir(newDeepCopy)

In the code above, we need to copy a complex object. The object userInfo contains value and reference type variable(“address”). The “address” property point to a reference object. Using JSON.stringify, we create a JSON string for the entire object userInfo. When we parse this JSON string into the object a separate reference is created for each of the complex and value type properties of the source object. Hence, we’re able to create a deep Ccopy of userInfo.

Since we’re achieving a deep copy of the source object, updating the streetNumber property in userInfo, will not impact the property of newDeepCopy Object, since they do not share the same reference.

This is the output for the code above:

There are some disadvantages to this method.

  1. The method cannot be used to copy the user-defined methods. The methods added to the object are not copied to the destination object.
var userInfo = {
name: "Mayank",
age: 31,
designation: "Developer",
getData: function() {
console.log(this.name);
}
}

var stringifiedObject = JSON.stringify(userInfo);

var newDeepCopy = JSON.parse(stringifiedObject);

console.dir(newDeepCopy)

The output for the following deep copy object is:

As you can see, using JSON.parse, the user-defined function in the object is not copied to the destination object. So this method is not a fool-proof way to deep copy an object.

2. This method cannot copy the “non-enumerable” properties.

The problem with this approach is that the non-enumerable properties cannot be copied. Internally, JSON.stringify iterates through the enumerable properties of an object and adds it to the string representation.

var employeeData = {
name: "Mayank",
age: 31,
designation: "Developer"
}

Object.defineProperty(employeeData, 'salary', {
value: 10000,
enumerable: false
});

var objectString = JSON.stringify(employeeData);

var destinationObject = JSON.parse(objectString);

console.dir(destinationObject)

The output for this execution is shown below:

The resulting object does not contain “salary” property which was the non-enumerable property of the userInfo object.

3. Cannot copy “prototype” properties and functions of the object.

The code below creates an object from the constructor function — showData is associated with the prototype property of constructor function. If we try to copy the object using this method, the prototype functions are not copied.

function Employee() {
this.name = "Mayank";
this.age = 20;
}

Employee.prototype.showData = function() {
console.log("User Name: " + this.name)
}

var userDetails = new Employee();

var stringObject = JSON.stringify(userDetails);

var destinationObject = JSON.parse(stringObject);

console.dir(destinationObject)

The output of the above execution is given below. We do not have any Prototype Functions associated with the destinationObject.


3. Copy Objects with “Object.assign”

This is another method used to create copies of the object. Object.assign is used to copy all the enumerable properties of all the object specified — it can copy properties from multiple objects. Let’s look at how this function is used in more detail.

This is the output from the above execution:

Let’s look at the advantages and disadvantages of this method.

Disadvantages of using Object.assign:

  1. “Object.assign” cannot copy through non-enumerable properties.

The assign method does not copy the non-enumerable properties of the Object. If the property or method is non-enumerable, it won’t be copied.

Here is an example:

var employeeData = {
name: "Mayank",
age: 31,
designation: "Developer"
}

Object.defineProperty(employeeData, 'salary', {
value: 10000,
enumerable: false
});

var destinationObject = Object.assign(employeeData);

console.dir(destinationObject)

The output for the code above is shown below. We can see that the non-enumerable property salary is not the part of the object created.

2. Object.assign does not copy prototype properties and methods.

None of the prototype properties and objects are added to the copied object. Let’s see this in the example below:

function Employee() {
this.name = "Mayank";
this.age = 20;
}

Employee.prototype.showData = function() {
console.log("User Name: " + this.name)
}

var userDetails = new Employee();

var destinationObject = Object.assign(userDetails);

console.dir(destinationObject)

In the above code, the prototype function showDetails is not copied to the destinationObject. Object.assign only copies the properties available through Object.getOwnPropertyNames. Therefore, this method cannot be used for copying prototype properties and functions. The output for the above execution is shown below:

3. Does not create a deep copy of the source object

This method does not create a deep copy of Source Object, it makes a shallow copy of the data. For the properties containing reference or complex data, the reference is copied to the destination object, instead of creating a separate object.

Advantages of Object. assign:

1. Functions can be copied from source to destination object.

function Employee() {
this.name = "Mayank";
this.age = 20;
this.showDetails = function() {
console.dir(this.name)
}
}

var userDetails = new Employee();

var destinationObject = Object.assign(userDetails);

console.dir(destinationObject)

The code above copies the user defined functions from the source object to destination object. The output also contains the showDetails function also.

2. Object.assign can be used to copy multiple object properties.

var firstObject = {
userName: "Mayank",
age: 30,
designation: "Developer"
}

var secondObject = {
street: "Rohini",
country: "India"
}

var destinationObject = Object.assign(firstObject, secondObject);

console.dir(destinationObject)

All the properties which are enumerable from all the objects specified in the array are copied to the destination object.


4. Working with Object.create to Copy Object

Object.create is another function that can make the properties of one object available to other objects. This method creates a new object using the existing object as a prototype, following the concept of prototype inheritance. The existing object is made available as a prototype making all the properties available to the new object.

Let’s see this method in detail.

var userInfo = {
name: "Mayank",
age: 30,
salary: 10000,
designation: "Developer"
}

var newObject = Object.create(userInfo);

console.log(newObject)

In the above code, we are using Object.create, to create a new object. Using this method, we do not add properties to the new object. The new object we have created does not contain any properties like name, age, and salary. See the image below:

The output shows that none of the property is added to the new object created. How can we still access the name property using this Object?

As stated earlier, the object userInfo is not available in the prototype chain of the newly created object. So when we access the property “name”, it will be accessed from the prototypal chain of the object.

The properties are available in the __proto__ property of the newly created object, which points to the object in the prototypal chain, hence making properties available to the object.

The above lines of code state that userInfo.name and newObject.name point to the same memory location. And userInfo does not contain the separate implementation of this property.

Let’s look at the advantages and disadvantages of this method.

Advantages of Object.create:

  1. All the methods and properties which are available to the userInfo object are available to the new object that is created, since it exists as the part of prototypal chain.

2. The Property is made available through the prototypal chain, so the object need not consume more memory space to save the existing functions and properties. No separate memory space is consumed, so the methods and the properties can be reused.

This method provides function and property re-usability in cases when we have some hierarchical data representation, or if we need to show some inheritance traits from source object to destination object

Disadvantages of Object.create:

  1. The newly created object does not create a separate property. Modifying the property from the Source Object modifies the data available to the new Object also.

The developer may assume that the property belongs to the newly created object. Modifying the property or function in source object are reflected in the destination or the new object created.

If you want to have a dedicated name property for each object created using Object.create, we need to add property explicitly to newly created object. In the code below, we’ve added a separate property to the new object created — in this case it will not access the name data from prototype chain.

var userInfo = {
name: "Mayank",
age: 30,
salary: 10000,
designation: "Developer"
}

var newObject = Object.create(userInfo);

newObject.name = "New Name";

2. Does not create a deep copy for the object.

It does not create the deep copy — the object follows the concept of inheritance. The new object does not contain separate properties and methods. The properties and methods are accessed from the parent in the inheritance chain, so a separate object is created.


5. Using Recursion for Object Copying

Recursion is the most reliable and effective method to ensure deep copying and maximum scenario coverage. In most of the scenarios above the methods do not provide a deep copy of the object being copied. Using recursion we can try to create an effective way to deep copy the object.

Concept of recursion in deep copy

Recursion can be used to create a deep copy of the source object. We use recursion to ensure that we cover the maximum possible scenarios. We can iterate through the object and copy the value type variables of the object. If the source object contains some complex objects, we can iterate through the complex object properties recursively and copy those properties to the destination object.

Shown below is the object representing the source object to be copied. The object contains value and reference types along with a function. We want to create a deep copy of this object. We need to ensure that the function is copied and a separate instance of address object is created. address represents a reference type in the object.

We’re not taking a more complex scenario in the code below — we’ll restrict the complexity of the source object to have a reference variable, along with functions and value types.

var userDetails = {
name: "Mayank",
age: 30,
designation: "Developer",
address: {
street: "Rohini",
country: "India"
}, showDetails: function() {
console.log("UserName: " + this.name);
}
}

Let’s take a look at the code to create a deep copy of the source object userName:

var userDetails = {
name: "Mayank",
age: 30,
designation: "Developer",
address: {
street: "Rohini",
country: "India"
}, showDetails: function() {
console.log("UserName: " + this.name);
}
}

var destinationObject = {};

function deepCopy(sourceObject, destinationObject) {

for(key in sourceObject) {
if(typeof sourceObject[key] != "object") {
destinationObject[key] = sourceObject[key];
} else {
destinationObject[key] = {};
deepCopy(sourceObject[key], destinationObject[key]);
}
}
}

deepCopy(userDetails, destinationObject);

In this code, we are using for-in to iterate through the list of properties inside the source object (userName). We iterate through each key in the object and look at whether the property represented by key is a complex object or not. If the key represents a complex object, we assign an empty object to the same key in the destination object and recursively calls for the deepCopy function sending sourceObject[key] as sourceObject parameter and destinationObject[key] as destinationObject parameter to the function. It iterates over the property of the complex object again and assigns it to the same key in the destination object.

This method can help us achieve a deep copy of an object. We can also extend this thought process to cater to more complex scenarios.

Better Programming

Advice for programmers.

Mayank Gupta

Written by

9 Years of Experience with Front-end Technologies and MEAN Stack. Working on all Major UI Frameworks like React, Angular and Vue.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade