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:
Here's what's happening step by step:
- We declare a variable
x
and assign it the value10
. - We define a function
incrementValue
that takes a parameternum
. - Inside the
incrementValue
function, we increment the value ofnum
by 1 usingnum = num + 1
. Since we passedx
(which is10
) as an argument when calling the function,num
is initially10
, so after incrementing, it becomes11
. However, this change only affects the local variablenum
inside the function, not the original value ofx
outside the function. - We log the value of
num
inside the function usingconsole.log(`Inside function: num = ${num}`)
, which outputsInside function: num = 11
. - We call the
incrementValue
function and pass the value ofx
(which is10
) as an argument. - After the function call, we log the value of
x
outside the function usingconsole.log(`Outside function: x = ${x}`)
, which outputsOutside 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:
Here's what's happening step by step:
- We declare a variable
person
and assign it an object with a propertyname
set to'John'
. - We define a function
changeName
that takes a parameterobj
. - Inside the
changeName
function, we change thename
property of the object thatobj
refers to by assigning it the value'Jane'
. Since we passed theperson
object as an argument when calling the function,obj
now refers to the same object asperson
. - We log the value of
person.name
before calling the function usingconsole.log(\
Before function call: person.name = ${person.name}`), which outputs
Before function call: person.name = John`. - We call the
changeName
function and pass theperson
object as an argument. - After the function call, we log the value of
person.name
again usingconsole.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¶
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¶
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¶
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¶
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¶
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¶
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¶
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¶
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¶
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¶
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.