# Making a basic deepCopy function in JS

**Important - If planning to read this article, do it completely as there are some corrections done later.**

Okay let's start 👇

By default in JS, if we try to make a copy of an object, say `obj`, then either of the two helps us create **Shallow** copies :-

* `Object.assign({}, obj)`
    
* `{...obj}`
    

And the notorious yet popular `JSON.parse(JSON.stringify(obj))` workaround can help us make a deep copy with the following limitations :-

* If `obj` has **methods**, they won't be copied.
    
* If `obj` has **circular references**, the above would simply throw an error.
    

This gives us an opportunity to make our own `deepCopy` function which can deal with the above limitations.

Let's dive into its epic creation via a conversation between **Shalu** and **Deepu**.

**Shalu** - I had a JS interview today and the interviewer asked me to build a custom `deepCopy(obj)` function to do guess what ? DEEP COPYING !!! But I only knew `JSON.parse(JSON.stringify(obj))` workaround which clearly had limitations as pointed by the interviewer.

**Deepu** - Don't worry. We will try to implement our own basic `deepCopy(obj)` function which also takes care of those limitations. We will start simple, and gradually transform our function for the requirements. Take a look at this function :-

```js
function deepCopy(obj) {
  const newObj = Array.isArray(obj) ? [] : {};
  for (const [key, value] of Object.entries(obj)) {
    newObj[key] = typeof value === 'object' ? deepCopy(value) : value;
  }
  return newObj;
}
```

**Shalu** - Well that's not gradual at all....

**Deepu** - Okay wait....

![let me explain](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061901947/CRdLOAP5c.jpeg align="left")

```js
const newObj = Array.isArray(obj) ? [] : {};
```

**Deepu** - We are initializing `newObj` to an empty `Array` or a `POJO` (Plain Old JavaScript Object) on basis of whether `obj` is an array or not.

```js
for (const [key, value] of Object.entries(obj)) {
    newObj[key] = typeof value === 'object' ? deepCopy(value) : value;
  }
  return newObj;
```

Suppose `obj` was `{ name:'Saitama', age:'26' }`, then `Object.entries(obj)` would return an array`[ ['name','Saitama'],['age','26'] ]`.

So we are looping over de-structured `key`\-`value` pair from this array and performing a conditional check.

The check is that if type of `value` is `object`, then assign the result of `deepCopy(value)` to `newObj[key]` else just assign `value` itself.

**Shalu** - Wait a minute !!! We are calling `deepCopy(...)` from within `deepCopy(...)`. Isn't that recursion ?

**Deepu**

![goddamn right](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061903563/cH9Mu7t-F.jpeg align="left")

This use-case requires recursion. We don't know how many layers of nested objects our main `obj` might have. We only know that if the corresponding `value` for a `key` is not of type `object`, we can safely put the same `key`\-`value` pair in our `newObj`. For the rest, we need to call `deepCopy(value)` again.

**Shalu** - But wait !!! What about **Functions** ? They are also JS Objects only right ?

**Deepu**

![obama you are right](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061906693/XB_OEJ6cN.jpeg align="left")

They indeed are but their `typeof` is `function`. And this particular thing really works for us since we only need to assign these functions as `value` to a particular `key` and not worry about any nesting which is in the case of `{ }` or `[ ]`.

**Shalu** - So this is it right ?

**Deepu** - Well not quite yet. The above will fail tragically in the case of circular references.

![no failure](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061908394/dT61Z2ypd.gif align="left")

**Shalu**

![why tell me why](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061910362/1bnRVtWV4.gif align="left")

**Deepu** - Remember how we are recursing whenever the type of `value` is `object` ? Now consider that after 3 depths of recursion, we arrive at a `key` whose `value` is again the main `obj` i.e. there is a circular reference from a nested `key` to the main `obj` itself. This will result in an infinite loop of menace !!

![infinity](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061912551/VKaYzABtE.gif align="left")

**Shalu** - Oh damn!!! How would you handle this ?

**Deepu** - Well let's see what do we have at disposal. We need a mechanism to not recurse over already processed or seen object references.

**Shalu** - Cool so let's make a new obj, say , `const seen = { }` and use it as a dictionary.

**Deepu** - Well we need object references as key and `{ }` only takes **strings** as **keys**.

**Shalu**

![Pikachu meme face](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061914393/Wn9ZiP0Pw.png align="left")

**Deepu** - We can make use of `Map` or `Set` here with the latter making more sense. And to take things up a notch, let's make use of `WeakSet`.

**Shalu** - Why `WeakSet` ?

**Deepu** - Because [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) says so !!

> Functions that call themselves recursively need a way of guarding against circular data structures by tracking which objects have already been processed. WeakSets are ideal for this purpose.

**Shalu** - Alright I am excited for the final code

![excited](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061916384/hduv2sAbv.gif align="left")

**Deepu**

![here we go](https://media0.giphy.com/media/LpkBAUDg53FI8xLmg1/giphy.gif?cid=790b76115cadcb0d567770466f6952c3 align="left")

```js
 function deepCopy(obj) {
  const seen = new WeakSet();

  function logic(obj) {
    const newObj = Array.isArray(obj) ? [] : {};
    if (!seen.has(obj)) {
      seen.add(obj);
      for (const [key, value] of Object.entries(obj)) {
        newObj[key] = typeof value === 'object' ? logic(value) : value;
      }
    } else {
      return obj;
    }
    return newObj;
  }
  
  return logic(obj);
}
```

**Shalu** - Damn that's quite big now.

**Deepu** - Well the flow is still simple. What we now did is initialize a `WeakSet` by the name `seen` inside `deepCopy(...)`. And since we always needed access to `seen` while recursing, we extract all our recursion logic inside this `logic(...)` function. Also note we have applied the check using `seen` for the `obj` reference and if it doesn't exist, we add it to `seen`. Else, we don't bother performing the **for loop** logic for it and return the `obj` as it is. At the end of `deepCopy(...)` function we call `logic(obj)` (which will internally recurse as needed) as well as return its result.

**Shalu**

![wow](https://cdn.hashnode.com/res/hashnode/image/upload/v1625061921742/oUeoWPEuD.gif align="left")

**Thank you everyone who read it till here. This is an implementation that I have tried without referring anything online with the mindset that how will I do this if asked in an interview. Obviously the flow will be the same minus the incredible gifs 😉 and you are free to evaluate me as an interviewer**.

### Correction

I got an important feedback that the above implementation doesn't clone the circular reference cycle successfully because I am returning the original `obj` when it's already present in `seen`. I should have been returning `newObj` corresponding to that `obj` here. For that, we would get rid of `WeakSet` altogether and use `WeakMap` instead like so :-

```js
 function deepCopy(obj) {
  const seen = new WeakMap();

  function logic(obj) {
    const newObj = Array.isArray(obj) ? [] : {};
    if (!seen.has(obj)) {
      seen.set(obj, newObj);
      for (const [key, value] of Object.entries(obj)) {
        newObj[key] = typeof value === 'object' ? logic(value) : value;
      }
    } else {
      return seen.get(obj);
    }
    return newObj;
  }

  return logic(obj);
}
```

### Possible enhancement - 1

```js
 function deepCopy(obj) {
  const seen = new WeakMap();

  function logic(obj) {
    // Creating dynamic newObj using constructor
    const newObj = new obj.constructor();
    if (!seen.has(obj)) {
      seen.set(obj, newObj);
      for (const [key, value] of Object.entries(obj)) {
        newObj[key] = typeof value === 'object' ? logic(value) : value;
      }
    } else {
      return seen.get(obj);
    }
    return newObj;
  }

  return logic(obj);
}
```

### BONUS - Fancy Reduce edit

```js
function deepCopy(obj) {
  const seen = new WeakMap();

  function logic(obj) {
    if (!seen.has(obj)) {
      return Object.entries(obj).reduce((newObj, [key, value]) => {
        seen.set(obj, newObj);
        newObj[key] = typeof value === 'object' ? logic(value) : value;
        return newObj;
      }, new obj.constructor())
    } else {
      return seen.get(obj);
    }
  }

  return logic(obj);
}
```
