DEV Community

Cover image for Memory And References
Naba Zehra
Naba Zehra

Posted on

Memory And References

Introduction

Many JavaScript developers know that objects are copied by reference, but few understand what that actually means.


The Problem

Consider the following code:

const user = {
  name: "Aline"
};

const copy = user;

copy.name = "Ali";
Enter fullscreen mode Exit fullscreen mode

Why Does Changing copy Also Change user?

Imagine you're working on a production application.

You create a copy of an object so you can safely modify it.

const user = {
  name: "Aline"
};

const copy = user;

copy.name = "Ali";

console.log(user.name);
Enter fullscreen mode Exit fullscreen mode

What do you expect to see?

Is your answer is:

"Aline"
Enter fullscreen mode Exit fullscreen mode

After all, we changed copy, not user.

But JavaScript prints:

"Ali"
Enter fullscreen mode Exit fullscreen mode

Wait...

How did changing copy affect user?

Did JavaScript secretly update both variables?

Did it create some hidden connection between them?

To answer that question, we need to look inside JavaScript's memory.


Let's Explore

When JavaScript executes:

const user = {
  name: "Aline"
};
Enter fullscreen mode Exit fullscreen mode

it creates an object somewhere in memory.

For simplicity, imagine JavaScript stores that object at address 0x001.

Heap

0x001
┌─────────────┐
│{            │
│ name:Aline  │
│}            │
└─────────────┘
Enter fullscreen mode Exit fullscreen mode

The variable itself is stored separately.

Instead of storing the entire object, it stores only the object's address.

Stack

user ──────► 0x001
Enter fullscreen mode Exit fullscreen mode

Think of it like a house address.

The object is the house.

The variable only stores the address of that house.


The Mistake We Make

When they see:

const copy = user;
Enter fullscreen mode Exit fullscreen mode

many of you imagine this:

user ──────► Object A

copy ──────► Object B
Enter fullscreen mode Exit fullscreen mode

Two separate objects.

Two separate locations in memory.

But that's not what happens.

JavaScript does not create a new object.

It simply copies the address.

Stack

user ──────► 0x001
copy ──────► 0x001
Enter fullscreen mode Exit fullscreen mode

Both variables now point to exactly the same object.

There is only one object in memory.


The Moment Everything Makes Sense

Now JavaScript executes:

copy.name = "Ali";
Enter fullscreen mode Exit fullscreen mode

It follows the address stored inside copy.

copy ──────► 0x001
Enter fullscreen mode Exit fullscreen mode

and updates the object found there.

Heap

0x001
┌─────────────┐
│ {           │
│ name:Ali    │
│ }           │
└─────────────┘
Enter fullscreen mode Exit fullscreen mode

But user points to that exact same object.

user ──────► 0x001
Enter fullscreen mode Exit fullscreen mode

So when we read:

console.log(user.name);
Enter fullscreen mode Exit fullscreen mode

JavaScript finds:

"Ali"
Enter fullscreen mode Exit fullscreen mode

Suddenly the behavior is no longer strange.

There were never two objects.

There was only one.


Creating a New Object

So how do we create a new object?

Most of use the spread operator.

const copy = {
  ...user
};
Enter fullscreen mode Exit fullscreen mode

Now JavaScript creates a second object.

Stack

user ──────► 0x001
copy ──────► 0x002
Enter fullscreen mode Exit fullscreen mode

Heap

0x001               0x002

{                    {
  name:"Aline"          name:"Aline"
}                    }
Enter fullscreen mode Exit fullscreen mode

Perfect.

Problem solved.

Or is it not?


The Hidden Trap

Let's make the object slightly more realistic.

const user = {
  name: "Aline",
  address: {
    city: "Karachi"
  }
};

const copy = {
  ...user
};
Enter fullscreen mode Exit fullscreen mode

Question:

What happens if we execute:

copy.address.city = "Lahore";
Enter fullscreen mode Exit fullscreen mode

Will user.address.city stay "Karachi"?

Most people confidently answer:

Yes, because we created a copy.

Unfortunately, JavaScript has another surprise waiting for us.


Looking Deeper Into Memory

This time the memory looks like this:

Stack

user ──────► 0x001
copy ──────► 0x002
Enter fullscreen mode Exit fullscreen mode

Heap

0x001
{
  name:"Aline",
  address ───► 0x100
}

0x002
{
  name:"Aline",
  address ───► 0x100
}

0x100
{
  city:"Karachi"
}
Enter fullscreen mode Exit fullscreen mode

Notice something interesting.

The outer objects are different.

0x001 ≠ 0x002
Enter fullscreen mode Exit fullscreen mode

But the nested object is shared.

address ───► 0x100
Enter fullscreen mode Exit fullscreen mode

Both objects point to the same nested object.

This is called a shallow copy.

The first level is copied.

Nested objects are not.


The Bug Appears

Now JavaScript executes:

copy.address.city = "Lahore";
Enter fullscreen mode Exit fullscreen mode

It follows:

copy
 ↓
0x002
 ↓
address
 ↓
0x100
Enter fullscreen mode Exit fullscreen mode

and updates the shared object.

0x100
{
  city:"Lahore"
}
Enter fullscreen mode Exit fullscreen mode

Since user.address also points to 0x100, it sees the update too.

console.log(user.address.city);
Enter fullscreen mode Exit fullscreen mode

Output:

"Lahore"
Enter fullscreen mode Exit fullscreen mode

Even though we never modified user.


Why structuredClone() Exists

We kept running into this problem.

They wanted a copy that duplicated everything.

Not just the first level.

Not just the outer object.

Everything.

That's why JavaScript introduced:

const copy = structuredClone(user);
Enter fullscreen mode Exit fullscreen mode

Now JavaScript creates completely separate nested objects.

user ──────► 0x001
copy ──────► 0x002


0x001.address ──────► 0x100

0x002.address ──────► 0x200
Enter fullscreen mode Exit fullscreen mode

Different outer objects.

Different nested objects.

No shared references.

No accidental mutations.

No surprises.

And that's the real difference between a shallow copy and a deep copy.


Quick Challenge

Without running the code, predict the output:


js
const user = {
  profile: {
    city: "Karachi"
  }
};

const copy = { ...user };

copy.profile.city = "Lahore";

console.log(user.profile.city);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)