Passing arguments by value or by reference
When we pass arguments into a function, JavaScript copies them, and how exactly it copies depends on the data type: primitives are copied by value, while objects, arrays and functions are passed by reference, or more precisely by a copy of the reference. That is why some changes made inside a function are visible outside and others disappear with the call.
Theory
TL;DR
- Primitives (
number,string,boolean,null,undefined,symbol,bigint) are passed by value: the parameter gets a full copy. - Objects, arrays and functions are passed by reference, that is, the parameter gets a copy of the reference to the same region of memory.
- Changing the contents of an object inside a function (
obj.name = "Bob",arr.push(4)) is visible in the original. - Reassigning the parameter (
obj = {...},arr = [...]) does not touch the original, because only the local copy of the reference changes. - Strictly speaking, JavaScript always passes by value: for objects that value is a reference.
Quick example
let a = 10;
function changeValue(x) {
x = 20; // we change the copy
}
changeValue(a);
console.log(a); // 10The value of a did not change, because only its copy was written into x.
Passing by value
Primitives, that is numbers, strings, booleans, null, undefined, symbol and bigint, are copied in full.
| Data type | How it is passed |
|---|---|
Primitives (numbers, strings, booleans, null, undefined, symbol, bigint) | by value |
| Objects, arrays, functions | by reference (more precisely, by a copy of the reference) |
Explanation for the example above:
- A copy of the value of
ais written intox. - Changing
xhas no effect onawhatsoever.
In memory it looks like this:
a -> 10
x -> 10 (copy)Passing by reference
Objects, arrays and functions are not copied whole. Instead, a reference to their address in memory is passed.
const user = { name: "Alice" };
function rename(obj) {
obj.name = "Bob";
}
rename(user);
console.log(user.name); // "Bob"Explanation:
- The variable
userholds a reference to the object in memory. - That same reference is copied into
obj, so both variables point to one and the same object. - Changes made through
objare visible throughuseras well.
In memory it looks like this:
user --+
v
{ name: "Bob" }
^
obj ---+Reassigning the reference inside a function
If you change the reference itself inside the function, the original stays untouched.
const user = { name: "Alice" };
function reassign(obj) {
obj = { name: "Charlie" }; // a new reference
}
reassign(user);
console.log(user.name); // "Alice"What happens here:
- The reference to
useris first copied intoobj. - Then
objis redirected to a new object, while the original reference (user) stays the same.
This is exactly the proof that a copy of the reference is passed, not the variable slot itself.
Array example
An array is an object too, so the same rules apply. Changing the contents is visible outside:
const numbers = [1, 2, 3];
function modify(arr) {
arr.push(4); // we change the contents
}
modify(numbers);
console.log(numbers); // [1, 2, 3, 4]But reassigning the reference is not:
function modify(arr) {
arr = [9, 9, 9]; // we create a new array
}
modify(numbers);
console.log(numbers); // [1, 2, 3]Summary and a simple analogy
| Data type | How it is passed | Does a change inside the function affect the original? |
|---|---|---|
Primitives (number, string, boolean, null, undefined, symbol, bigint) | By value | No |
| Objects, arrays, functions | By reference (a copy of the reference) | Yes, if the contents change |
| Reassigning the parameter inside the function | A local copy | No |
An analogy that works well in an interview:
- By value is like handing over a photocopy of a sheet of paper: if you write something on your copy, the original does not change.
- By reference is like handing over the address of a safe: both of you can open the same safe and change what is inside.
Common mistakes
- Believing that JavaScript has true pass by reference. It does not: a function cannot reassign the variable it was given. The accurate wording is "passing a copy of the reference", also known as call by sharing.
- Confusing mutation with reassignment.
obj.name = "Bob"changes the shared object,obj = {...}changes only the local parameter. - Thinking that
constprotects against changes.const user = {...}forbids reassigninguser, but it does not forbid changing its properties inside a function. For that you needObject.freezeor a copy. - Expecting a shallow copy to solve everything.
{ ...user }andstructuredClonebehave differently: the spread copies only the top level, so nested objects stay shared. - Forgetting that a string is immutable. A method such as
str.toUpperCase()does not change the argument, it returns a new string that you have to assign.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.