Pass by value versus pass by reference is where a huge share of "why did my variable change?" bugs come from. The concept is simple in theory: pass by value gives the function a copy of the argument, so changes stay local; pass by reference gives the function the caller's actual variable, so changes leak back out. The trouble is that most popular languages do neither in the pure form people expect, and the mismatch between the mental model and reality is exactly where mutation bugs hide.
What changed in 2026
- Immutability-by-default kept gaining ground. More languages and libraries default to immutable data structures, which sidesteps the whole class of accidental-mutation bugs.
- Linters flag risky mutation. Static analysis tools increasingly warn when a function mutates a shared argument, catching the classic mistake before review.
- Value types got more attention. Several languages expanded first-class value (copy) types so developers can opt into copy semantics deliberately.
- The vocabulary settled. "Call by sharing" is now the commonly taught term for what most object-oriented languages actually do, reducing decades of confusion.
What the two terms really mean
Pass by value means the function receives a copy of the argument. Whatever it does to its parameter, the caller's original is untouched.
Pass by reference means the function receives a reference to the caller's variable itself. If the function reassigns the parameter, the caller sees the new value.
// true pass by reference (e.g. C++ &)
void reset(int& x) { x = 0; }
int a = 5; reset(a); // a is now 0
The clean test for true pass by reference: can the function reassign the caller's variable and have it stick? In genuinely pass-by-reference languages, yes. In most mainstream languages, no.
Why most languages confuse everyone
Languages like the popular scripting and JVM-family ones pass everything by value — but for objects, the value being copied is a reference. This is called call by sharing. The function gets its own copy of the reference, both pointing at the same underlying object.
The consequence trips up nearly everyone:
function mutate(list) { list.push(4); } // affects caller: shared object
function reassign(list) { list = [9]; } // does NOT affect caller: local copy of the reference
mutate changes the shared object, so the caller sees 4 added. reassign only rebinds the local copy of the reference, so the caller's variable is unchanged. Mutation leaks out; reassignment does not. Understanding this one distinction eliminates most of the confusion — and it is closely tied to how shared mutable state causes race conditions in concurrent code.
The three behaviors compared
| Behavior |
Copies |
Reassignment visible? |
Mutation visible? |
| Pass by value (primitive) |
The value |
No |
N/A |
| Call by sharing (object) |
The reference |
No |
Yes |
| Pass by reference (true) |
Nothing, aliases the variable |
Yes |
Yes |
The middle row is what most developers actually work with daily, and it is neither of the textbook extremes.
How to avoid the classic bugs
Know exactly what your language copies for primitives versus objects. When a function should not change its input, either work on a copy inside the function or, better, use immutable data structures so mutation is impossible by construction.
Prefer returning new values over mutating arguments. A function that takes data and returns a transformed copy is easier to reason about and test than one that quietly edits what it was given. When you genuinely need to mutate a shared structure for performance, make that intent explicit in the name and documentation so callers are not surprised.
Pitfalls
- Assuming objects are copied. They usually are not; you share the underlying object.
- Expecting reassignment inside a function to stick. In call-by-sharing languages it does not.
- Mutating a caller's list or map by accident. The most common real-world instance of this bug.
- Deep vs shallow copy confusion. Copying the outer object may still share nested objects.
FAQ
Is my language pass by value or pass by reference?
Most mainstream languages are strictly pass by value, but for objects the value copied is a reference (call by sharing). Truly pass-by-reference languages let a function reassign the caller's variable.
Why did my array change after a function call?
Because the function received a copy of the reference to the same array and mutated it. The object is shared even though the variable was passed by value.
What is call by sharing?
The behavior where a function gets its own copy of a reference to the same object. Mutations to the object are visible to the caller; reassigning the parameter is not.
How do I stop a function from changing my data?
Pass a copy, or use immutable data structures. Returning new values instead of mutating arguments is the most reliable pattern.
Where to go next