Reference vs value in JavaScript: what actually gets copied on assignment

Primitive values are copied, objects and arrays are shared by reference. That difference explains half of all assignment-related bugs.
3 min read
Believemy logo

Assigning one variable to another always looks like the same operation. With a number, the second variable becomes independent. With an object, both names point at the same thing, and changing one changes the other.

This asymmetry is not a JavaScript oddity: most languages work that way. You still have to know which side each type sits on.


Definition

Three lines that look like a copy, and the one that really protects the original
1. The copy lineconst copy =
2. What you then write into the copy
The original
The copy
A number: two values, no objectlet a = 10; let b = a; b += 5;
a
10
b
15
The profile object
profile
name: "Ada"
address: ↓
copy
name: "Grace"
address: ↓
The address object, one level down
profile.addresscopy.addressshared
city: "London"
Objects in memory: 3Objects shared by both names: 1
a10unchanged
profile.name"Ada"unchanged

The first level really is detached: profile.name still reads "Ada". Now switch the target to copy.address.city, because that is where the protection stops.

The table is a simplified view of memory: on the last two rows every solid frame is one object, and a frame straddling both columns is a single object reached through both names. The dashed frames on top are values, not objects, and are left out of the count. { ...profile } only copies own enumerable properties at the first level, and freezes getters into plain values at that moment. structuredClone() has limits of its own: it refuses a function, and it returns the object without its prototype, so without its class methods. The number has no copy mode at all: a primitive is duplicated on every assignment, there is no address to share.

Change the copy and watch which original moved. The third case, the nested object, is the one that surprises most.

A primitive value is copied on every assignment: the new variable gets its own instance. An object is not: the variable only holds a reference, an address, and assigning it elsewhere merely duplicates that address.

JAVASCRIPT
let a = 10;
let b = a;
b += 5;
console.log(a, b); // 10 15

const objectA = { total: 10 };
const objectB = objectA;
objectB.total += 5;
console.log(objectA.total, objectB.total); // 15 15

In the second case, no object was created on the assignment line. There is one object and two names pointing at it.


What it changes for equality

Two objects with identical contents are never equal: the comparison looks at the address, not at the contents.

JAVASCRIPT
console.log({ x: 1 } === { x: 1 }); // false
console.log([1, 2] === [1, 2]);     // false

const shared = { x: 1 };
console.log(shared === shared);     // true

That holds for Strict equality (===) as much as for Loose equality (==). To compare contents you either walk field by field, or serialize through JSON, keeping in mind that key order then matters.


Copying for real

JAVASCRIPT
const profile = { name: "Ada", address: { city: "London" } };

const shallow = { ...profile };
shallow.address.city = "Paris";
console.log(profile.address.city); // Paris, the nested part is shared

const deep = structuredClone(profile);
deep.address.city = "Lyon";
console.log(profile.address.city); // Paris, the original is protected
Good to know

Spread (...) only copies one level. For a nested structure, structuredClone() produces a complete copy, dates and Maps included, where a round trip through JSON would have lost them.


Frequently asked questions

Question

Can a function modify an object passed as an argument?

Yes, the function receives the same reference and can change the contents. Reassigning the parameter inside, however, changes nothing for the caller: only the local copy of the address is replaced. That nuance is what separates modifying an object from replacing it.


Question

Why are two identical dates not equal?

Because a Date is an object, and therefore compared by reference. Two instances built on the same instant remain two distinct objects. The reliable comparison goes through their numeric value, with a.getTime() === b.getTime().


Question

Are strings really copied every time?

From the language point of view, yes: a string behaves like a value and cannot mutate. Internally, engines share memory as long as nobody modifies anything, but that optimization is invisible and changes no observable behavior.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.