· By Wataru Ito · Coding · 7 min read
Is JavaScript pass-by-value or pass-by-reference?
A pragmatic explanation of how JavaScript handles values and references, with clear examples and mental models for understanding assignment and equality.
TL;DR
Is JavaScript pass-by-value or pass-by-reference? It’s a classic debate that trips up even experienced devs. The answer isn’t as simple as you might think—let’s break it down together!
Yet Another JavaScript Blog Post
There are a lot of tech articles out there, seemingly narrating the same topics over and over again. Are they worth it? They are, in fact, worth it for the writers. Writing articles is a great way to solidify your understanding of a topic. And it air’nt gonna hurt any readers either.
That is the premise I am taking a courage from to produce yet another article (or more) about JavaScript.
Is JavaScript pass-by-value or pass-by-reference?
JavaScript passes arguments by value, including objects and arrays. For objects, you can think of it as copying a reference to the same object. You can change that object’s properties through the reference.
I first wrote that JavaScript passes objects by reference, with help from Copilot in VS Code. I accepted that explanation, but calling it “pass-by-reference” was a mistake. Reassigning a parameter inside a function does not change the caller’s variable.
The terms “pass-by-value” and “pass-by-reference” describe how we pass arguments to functions. I’ll start with variable assignment, then use a function example to show the difference.
Let’s start with a simple assignment:
let a = 10;
let b = a;
console.log(a); // 10
console.log(b); // 10
a = 20;
console.log(a); // 20
console.log(b); // 10What is happening here is simple. We create a, assign a value of 10 to it, then assign a to b. That time, the value of 10 is copied over to b. Then we reassign a value of 20 to a. Now a is 20, but b is still 10.
Then, consider this scenario:
let user = { name: "Josh", age: 30 };
let user2 = user;
user2.age = 24;
console.log("user.age: ", user.age); // what is the age now?In this case, we create an object user with properties name and age. Then we assign user to user2. We copy the reference, so both variables refer to the same object. We don’t create a separate copy of the object.
Therefore, the answer to the question “what is the age now?” is 24. Because we changed the age property of user2, which is a reference to the same object as user.
But I used to get confused about reassigning user2.
Copying a reference and reassigning a variable
Let’s consider this scenario:
let user = { name: "Josh", age: 30 };
let user2 = user;
user2.age = 24;
console.log("user.age: ", user.age); // what is the age now?
user2 = { name: "Henry", age: 84 };
console.log("user2.age: ", user2.age); // 84
console.log("user.age: ", user.age); // what is the age now?
console.log("user.name: ", user.name); // what is the name now?Some of you might got it right away, but if you are like me, you might assumed it is 84. But, it you run it in your browser console, you will see that user.age is still 24, and user.name is still Josh.
The reason is that when we assigned the value of { name: 'Henry', age: 84 } to user2, we created a new reference to a new object, which is { name: 'Henry', age: 84 }.
I find it helpful to think of a reference as an address for an object. We can copy that address and use it to reach the same object. In this model, the code looks like this:
let user = { name: "Josh", age: 30 }; // creates a new object, which is ` { name: 'Josh', age: 30 }` in memory/heap, and `user` holds an address to it; let's call it `000abc123`
let user2 = user; // `user2` now holds the same address as `user`, which is `000abc123`
user2.age = 24; // assign `24` to the `age` property of the object at address `000abc123`, which is the same object as `user`
console.log("user.age: ", user.age);
user2 = { name: "Henry", age: 84 }; // creates a new object `{ name: 'Henry', age: 84 }` in memory/heap, and `user2` now holds a new address, let's say `000def456`, while `user` still holds the address `000abc123`
console.log("user2.age: ", user2.age); // refers to the property `age` of the object located at `000def456`, which is `84
console.log("user.age: ", user.age); // refers to the property `age` of the object located at `000abc123`, which is `24`
console.log("user.name: ", user.name); // refers to the property `name` of the object located at `000abc123`, which is `Josh`I use these addresses as a mental model. JavaScript engines handle memory in different ways, and we cannot tell where they store a value from whether it is a primitive or an object. We don’t need those details to follow which object each variable refers to.
Passing an object to a function
We can use the same idea for function arguments:
function update(user) {
user.age = 24;
user = { name: "Henry", age: 84 };
}
const original = { name: "Josh", age: 30 };
update(original);
console.log(original); // { name: "Josh", age: 24 }Inside update, user refers to the same object as original. Through user.age = 24, we change that shared object. Then we assign a new object to the local parameter user. After the call, original refers to the Josh object with age 24.
With pass-by-reference, assigning to the parameter would change the caller’s variable too. In JavaScript, we pass the reference by value. You may see the term “pass-by-sharing” for this behavior with objects.
Comparing values and references
Let’s consider this:
const string1 = "hello";
const string2 = "hello";
console.log("string1 === string2: ", string1 === string2); // true
const object1 = { name: "Yoshi" };
const object2 = { name: "Yoshi" };
console.log("object1 === object2: ", object1 === object2); // falseThis is a typical example of explaining how referential equality works in “equality” check. When comparing string1 and string2, it is their value that is compared, so it is true. When comparing object1 and object2, it is their reference that is compared. Therefore, it is false.
const string1 = "hello";
const string2 = "hello";
console.log("string1 === string2: ", string1 === string2); // true
const object1 = { name: "Yoshi" };
const object2 = { name: "Yoshi" };
console.log("object1 === object2: ", object1 === object2); // falseTherefore, now we can interpret the above code as follows:
const string1 = "hello"; // string1's value is "hello"
const string2 = "hello"; // string2's value is also "hello"
console.log("string1 === string2: ", string1 === string2); // comparing "hello" and "hello", true
const object1 = { name: "Yoshi" }; // hypothetically object1's value is "000abc123" which is the address to the object `{ name: "Yoshi" }` in memory)
const object2 = { name: "Yoshi" }; // hypothetically object2's value is "000def456" which is the address to the object `{ name: "Yoshi" }` in memory)
console.log("object1 === object2: ", object1 === object2); // comparing "000abc123" and "000def456", false
let object3 = object1; // assigning "000abc123" to object3; it is let simply for the sake of experiments
console.log("object1 === object3: ", object1 === object3); // comparing "000abc123" and "000abc123", true
object3.name = "Henry"; // changing the name property of the object at address "000abc123" to "Henry"
console.log("object1.name: ", object1.name); // accessing the name property of the object at address "000abc123", which is now "Henry"
object3 = { name: "Jackson" }; // object3 now holds a new address, let's say "000def789", which is the address to the object `{ name: "Jackson" }` in memory
console.log("object1.name: ", object1.name); // object1 is intact, whose address is "000abc123", which still has the name of "Henry"
console.log("object3.name: ", object3.name); // accessing the name property of the object at address "000def789", which is "Jackson"
console.log("object1 === object3: ", object1 === object3); // now comparing "000abc123" and "000def789", false
This is a pragmatic mental model that has served me well so far, and I hope it does for you too when working with references. With React dominating the front-end world, you’ll likely find these concepts useful someday. Hopefully!
For me, they certainly saved the day, though I must admit I initially spent a few hours tearing my hair out because I had completely forgotten these concepts because of sleep deprivation because of my sweet little rascals at home.
I used AI to help check the accuracy of the explanations in this article.