Mona Alfonso

Mona Alfonso

Web Developer and Educator with 5+ years of experience in tech.

Main Idea: In JavaScript, primitive data types (such as numbers, strings, booleans, null, and undefined) are passed by value, while non-primitive data types (such as objects, arrays, and functions) are passed by reference.

First, a helpful analogy

Imagine you have written a book, and you want to give it to a friend. You make a copy and give it to them. Any changes your friend makes to their copy (e.g., writing notes, tearing pages) will not affect your original book. This is similar to how primitive values work in JavaScript. When you pass a primitive value (strings, numbers, booleans, undefined, or null) to a function, a copy of that value is created and passed to the function. Any changes made to the copy inside the function do not affect the original value outside the function.

Now, imagine you bought a book and loved it, and you want to share it with a friend, so you lend the book to them for them to read. Any changes your friend makes to the book (e.g., writing notes, tearing pages) will affect the original book that you both share. This is similar to how non-primitive values (like objects and arrays) work in JavaScript. When you pass a non-primitive value to a function, you’re not creating a copy; instead, you’re passing a reference to the original value. Any changes made to the value inside the function will affect the original value outside the function because they both refer to the same value in memory.

Explainer, a little redundant but can’t hurt to hear it again…

When you pass a primitive data type such as a number, string, boolean, null, or undefined (for example, into a function as an argument), you are passing a copy of that value into the function. Any modifications or operations performed on that value inside the function will only affect the copy, and the original value outside the function remains unchanged.

However, when you pass a non-primitive data type, such as an array, object, or function (including callback functions), similarly into a function, you are not passing a copy of the value. Instead, you are passing a reference or pointer to the location in memory where that data structure is stored. Any changes or modifications made to the data structure inside the function will directly affect the original, because the function is working with a reference or pointer to the same data whereever it is in the memory.

In other words, for primitive data types, the function operates on a separate copy, leaving the original value untouched. But for non-primitive data types, the function operates directly on the original data structure, allowing any changes made inside the function to persist outside the function as well.

This behavior is known as "passing by value" for primitives and "passing by reference" for non-primitives, and it's an important concept to understand when working with functions and data structures in JavaScript.

Examples In Code

Passing by Value

When you pass a primitive data type to a function, a copy of the value is created and passed to the function. Any changes made to the parameter inside the function will not affect the original value outside the function.

Example:

let x = 10; // Declare a variable 'x' and assign it the value 10
 
function incrementValue(num) { // Define a function named 'incrementValue' that takes a parameter 'num'
  num = num + 1; // Increment the value of 'num' by 1 (num is now 11, but x is still 10)
  console.log(`Inside function: num = ${num}`); // Log the value of 'num' inside the function (Output: Inside function: num = 11)
}
 
incrementValue(x); // Call the 'incrementValue' function and pass the value of 'x' (10) as an argument
console.log(`Outside function: x = ${x}`); // Log the value of 'x' outside the function (Output: Outside function: x = 10)

Here's what's happening step by step:

  1. We declare a variable x and assign it the value 10.
  2. We define a function incrementValue that takes a parameter num.
  3. Inside the incrementValue function, we increment the value of num by 1 using num = num + 1. Since we passed x (which is 10) as an argument when calling the function, num is initially 10, so after incrementing, it becomes 11. However, this change only affects the local variable num inside the function, not the original value of x outside the function.
  4. We log the value of num inside the function using console.log(`Inside function: num = ${num}`), which outputs Inside function: num = 11.
  5. We call the incrementValue function and pass the value of x (which is 10) as an argument.
  6. After the function call, we log the value of x outside the function using console.log(`Outside function: x = ${x}`), which outputs Outside function: x = 10.

The key point here is that when we pass a primitive value (like a number) to a function, a copy of the value is created and passed to the function. Any changes made to the parameter inside the function will not affect the original value outside the function. This is known as "passing by value".

In Sum: In this example, the value of x (10) is passed to the incrementValue function. Inside the function, a new variable num is created with the value of 10. When we increment num to 11, it does not affect the original value of x outside the function.

Passing by Reference

When you pass a non-primitive data type (like an object or array) to a function, a reference to the original value is passed. Any changes made to the parameter inside the function will affect the original value outside the function.

Example:

let person = { name: 'John' }; // Declare a variable 'person' and assign it an object with a property 'name' set to 'John'
 
function changeName(obj) { // Define a function named 'changeName' that takes a parameter 'obj'
  obj.name = 'Jane'; // Change the 'name' property of the object that 'obj' refers to (obj now refers to the same object as person)
}
 
console.log(`Before function call: person.name = ${person.name}`); // Log the value of 'person.name' before calling the function (Output: Before function call: person.name = John)
changeName(person); // Call the 'changeName' function and pass the 'person' object as an argument
console.log(`After function call: person.name = ${person.name}`); // Log the value of 'person.name' after calling the function (Output: After function call: person.name = Jane)

Here's what's happening step by step:

  1. We declare a variable person and assign it an object with a property name set to 'John'.
  2. We define a function changeName that takes a parameter obj.
  3. Inside the changeName function, we change the name property of the object that obj refers to by assigning it the value 'Jane'. Since we passed the person object as an argument when calling the function, obj now refers to the same object as person.
  4. We log the value of person.name before calling the function using console.log(\Before function call: person.name = ${person.name}`), which outputs Before function call: person.name = John`.
  5. We call the changeName function and pass the person object as an argument.
  6. After the function call, we log the value of person.name again using console.log(\After function call: person.name = ${person.name}`), which outputs After function call: person.name = Jane`.

The key point here is that when we pass a non-primitive value (like an object) to a function, a reference to the original value is passed. Any changes made to the parameter inside the function will affect the original value outside the function. This is known as "passing by reference".

In this example, when we change the name property of the object that obj refers to inside the changeName function, we're actually modifying the original person object because obj and person refer to the same object in memory.

It's important to understand this behavior when working with objects and arrays in JavaScript, as modifying them inside a function can have unintended consequences if you're not aware of the passing by reference mechanism.

In Sum: In this example, the person object is passed to the changeName function. Inside the function, the obj parameter refers to the same object as person. When we change the name property of obj, it directly modifies the original person object outside the function.

Practice Problems (Easier to Harder)

Practice Problem 1

let x = 5;
let y = x;
y = y + 1;
console.log(x); // What is the output?

Answer: The output is 5. When we assign y = x, we create a copy of the value 5 and store it in y. Incrementing y to 6 does not affect the original value of x.

Practice Problem 2

let str = "hello";
function changeString(s) {
  s = "goodbye";
}
changeString(str);
console.log(str); // What is the output?

Answer: The output is "hello". When we pass the string "hello" to the changeString function, a copy of the string is created and stored in the parameter s. Changing s to "goodbye" inside the function does not affect the original str variable outside the function.

Practice Problem 3

let arr = [1, 2, 3];
function modifyArray(a) {
  a.push(4);
}
modifyArray(arr);
console.log(arr); // What is the output?

Answer: The output is [1, 2, 3, 4]. When we pass the array [1, 2, 3] to the modifyArray function, a reference to the original array is passed. Modifying the array by calling a.push(4) inside the function affects the original arr array outside the function.

Practice Problem 4

let obj = { name: "John", age: 30 };
function incrementAge(o) {
  o.age++;
}
incrementAge(obj);
console.log(obj.age); // What is the output?

Answer: The output is 31. When we pass the object { name: "John", age: 30 } to the incrementAge function, a reference to the original object is passed. Incrementing o.age inside the function modifies the age property of the original obj object outside the function.

Practice Problem 5

let x = 10;
function incrementValue(num) {
  num++;
  return num;
}
x = incrementValue(x);
console.log(x); // What is the output?

Answer: The output is 11. When we call incrementValue(x), a copy of the value 10 is passed to the function parameter num. Inside the function, num is incremented to 11, and this new value is returned. The returned value 11 is then assigned back to x.

Practice Problem 6

let person = { name: "Alice" };
let anotherPerson = person;
anotherPerson.name = "Bob";
console.log(person.name); // What is the output?

Answer: The output is "Bob". When we assign anotherPerson = person, both variables person and anotherPerson refer to the same object in memory. Modifying anotherPerson.name to "Bob" also changes the name property of the object that person refers to.

Practice Problem 7

let obj1 = { a: 1 };
let obj2 = { b: 2 };
let obj3 = obj1;
obj3.a = 3;
obj2 = obj3;
console.log(obj1.a, obj2.a, obj2.b); // What is the output?

Answer: The output is 3, 3, undefined. Initially, obj1 and obj3 refer to the same object { a: 1 }. When we assign obj3.a = 3, it modifies the a property of the object that both obj1 and obj3 refer to. Then, when we assign obj2 = obj3, obj2 also starts referring to the same object { a: 3 }. However, this object does not have a b property, so obj2.b is undefined.

Practice Problem 8

let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
arr1 = [5, 6];
console.log(arr1, arr2); // What is the output?

Answer: The output is [5, 6], [1, 2, 3, 4]. Initially, arr1 and arr2 refer to the same array [1, 2, 3]. When we call arr2.push(4), it modifies the original array to [1, 2, 3, 4]. However, when we assign arr1 = [5, 6], arr1 now refers to a new array [5, 6], while arr2 still refers to the original modified array [1, 2, 3, 4].

Practice Problem 9

function changeObj(obj) {
  obj = { name: "Bob" };
}
let person = { name: "Alice" };
changeObj(person);
console.log(person.name); // What is the output?

Answer: The output is "Alice". When we pass the object { name: "Alice" } to the changeObj function, a reference to the original object is passed as the parameter obj. However, when we assign obj = { name: "Bob" } inside the function, we create a new object and assign it to the local variable obj. This does not affect the original person object outside the function.

Practice Problem 10

let obj = { a: 1, b: { c: 2 } };
let newObj = Object.assign({}, obj);
newObj.a = 3;
newObj.b.c = 4;
console.log(obj.a, obj.b.c); // What is the output?

Answer: The output is 1, 4. When we create newObj using Object.assign({}, obj), we create a shallow copy of obj. This means that the top-level properties (a and b) are copied, but nested objects (b.c) are still referenced by both obj and newObj. Modifying newObj.a to 3 only changes the a property of newObj, but modifying newObj.b.c to 4 also changes the c property of the nested object that both obj.b and newObj.b refer to.