In section 4.5 Method Parameters of his book Core Java, Cay S. Horstmann explains that Java always uses the call by value behaviour for method parameters, never call by reference. I have discovered that the same appears to be true of JavaScript (and Python, and many more languages, I am sure).
Call by value and call by reference
Regarding the call by value and call by reference behaviours, Horstmann writes:
“Let us review the computer science terms that describe how parameters can be passed to a method (or a function) in a programming language. The term call by value means that the method gets just the value that the caller provides. In contrast, call by reference means that the method gets the location of the variable that the caller provides. Thus, a method can modify the value stored in a variable passed by reference but not in one passed by value.”
Primitives are passed by value
The call by value behaviour is most obvious with primitive values, which are immutable. This means they cannot be mutated in the same way that objects can. A variable that is assigned to a primitive value can be reassigned to another primitive value, but the primitive value itself cannot be mutated. There are seven primitive data types in JavaScript:
number
bigint
boolean
string
symbol
null
undefined
For example, let us define a toggleBoolean(x)
function that attempts to change the value of a variable from true
to false
or from false
to true
.
function toggleBoolean(x) {
x = !x;
console.log(`End of function: x = ${x}`);
}
let trueOrFalse = true;
console.log(`Before: trueOrFalse = ${trueOrFalse}`);
toggleBoolean(trueOrFalse);
console.log(`After: trueOrFalse = ${trueOrFalse}`);
The output of this code is as follows. Note that the value of the trueOrFalse
variable is unchanged after the function call. This proves that primitive values are passed by value in JavaScript. If they were passed by reference, the value of the trueOrFalse
variable would change.
Before: trueOrFalse = true
End of function: x = false
After: trueOrFalse = true
The only way to “make this work” is to return the new value of the x
parameter from the toggleBoolean(x)
function and reassign the trueOrFalse
variable to it.
function toggleBoolean(x) {
x = !x;
console.log(`End of function: x = ${x}`);
return x;
}
let trueOrFalse = true;
console.log(`Before: trueOrFalse = ${trueOrFalse}`);
trueOrFalse = toggleBoolean(trueOrFalse);
console.log(`After: trueOrFalse = ${trueOrFalse}`);
The output of this code is as follows:
Before: trueOrFalse = true
End of function: x = false
After: trueOrFalse = false
Objects seem like they are passed by reference
Following on from this, Horstmann writes:
“You have seen that it is impossible to change a primitive type parameter. The situation is different for object parameters.”
For example, let us define an increment(x)
function that increments the value of an object’s .value
property:
function increment(x) {
x.value += 1;
console.log(`End of function: x.value = ${x.value}`);
}
const count = { value: 0 };
console.log(`Before: count.value = ${count.value}`);
increment(count);
console.log(`After: count.value = ${count.value}`);
The output of this code is as follows. Note that the value of the count.value
property is changed after the function call. This makes it seem like objects are passed by reference in JavaScript, but they are not!
Before: count.value = 0
End of function: x.value = 1
After: count.value = 1
Horstmann explains:
“As you have seen, it is easily possible—and in fact very common—to implement methods that change the state of an object parameter. The reason is simple. The method gets a copy of the object reference, and both the original and the copy refer to the same object.
“Many programming languages (in particular, C++ and Pascal) have two mechanisms for parameter passing: call by value and call by reference. Some programmers (and unfortunately even some book authors) claim that Java uses call by reference for objects. That is false. As this is such a common misunderstanding, it is worth examining a counterexample in detail.”
Object references are passed by value
Horstmann’s counterexample is a method that attempts to swap the values of two variables that are assigned to objects. Here is a similar example in JavaScript.
function swap(x, y) {
[x, y] = [y, x];
console.log(`\nEnd of function: x = ${x.name}`);
console.log(`End of function: y = ${y.name}`);
}
let a = { name: "Tom" };
let b = { name: "Jerry" };
console.log(`Before: a = ${a.name}`);
console.log(`Before: b = ${b.name}`);
swap(a, b);
console.log(`\nAfter: a = ${a.name}`);
console.log(`After: b = ${b.name}`);
The output of this code is as follows. Note that the objects are not swapped after the function call. This proves that object references are passed by value in JavaScript. If they were passed by reference, then the values of the a
and b
variables would be swapped.
Before: a = Tom
Before: b = Jerry
End of function: x = Jerry
End of function: y = Tom
After: a = Tom
After: b = Jerry
Again, the only way to “make this work” is to return the swapped values of the x
and y
parameters from the swap(x, y)
function and reassign the a
and b
variables to them.
function swap(x, y) {
[x, y] = [y, x];
console.log(`\nEnd of function: x = ${x.name}`);
console.log(`End of function: y = ${y.name}`);
return [x, y];
}
let a = { name: "Tom" };
let b = { name: "Jerry" };
console.log(`Before: a = ${a.name}`);
console.log(`Before: b = ${b.name}`);
[a, b] = swap(a, b);
console.log(`\nAfter: a = ${a.name}`);
console.log(`After: b = ${b.name}`);
The output of this code is as follows:
Before: a = Tom
Before: b = Jerry
End of function: x = Jerry
End of function: y = Tom
After: a = Jerry
After: b = Tom
Horstmann explains why the original does not work:
“However, the method does not actually change the object references that are stored in the variables
a
andb
. Thex
andy
parameters of theswap
method are initialized with copies of these references. The method then proceeds to swap these copies.“But ultimately, this is a wasted effort. When the method ends, the parameter variables
x
andy
are abandoned. The original variablesa
andb
still refer to the same objects as they did before the method call.”
Call by sharing
An article on Wikipedia titled Value type and reference type explains that this behaviour is sometimes known as call by sharing. Under the heading Reference types and “call by sharing,” it says:
‘Even when function arguments are passed using “call by value” semantics (which is always the case in Java, and is the case by default in C#), a value of a reference type is intrinsically a reference; so if a parameter belongs to a reference type, the resulting behavior bears some resemblance to “call by reference” semantics. This behavior is sometimes called call by sharing.
‘Call by sharing resembles call by reference in the case where a function mutates an object that it received as an argument: when that happens, the mutation will be visible to the caller as well, because the caller and the function have references to the same object. It differs from call by reference in the case where a function assigns its parameter to a different reference; when that happens, this assignment will not be visible to the caller, because the caller and the function have separate references, even though both references initially point to the same object.’
Summary
Horstmann summarizes all of this as follows:
- A method cannot modify a parameter of a primitive type.
- A method can change the state of an object parameter.
- A method cannot make an object parameter refer to a new object.