Pass by Value vs Pass by Reference
Why mutating an object inside a function works in Python and Java but reassigning the parameter doesn't — and how that differs from C copies and C++ references.
“Is this language pass by value or pass by reference?” sounds like a yes/no question, but it trips up experienced developers because the honest answer for most popular languages is “neither, exactly.” The confusion is worth clearing up once, because it explains a whole class of bugs.
The two real mechanisms
Pass by value means the function receives a copy of the argument. Whatever the function does to its parameter, the caller’s original variable is unaffected, because they are two separate pieces of memory.
Pass by reference means the function receives an alias — another name for the caller’s variable itself. Assigning to the parameter assigns to the caller’s variable. There is only one piece of memory, under two names.
C is strictly pass by value. When you pass an int, the function gets a copy:
void tryToChange(int x) { x = 99; } // local copy onlyint a = 1;tryToChange(a); // a is still 1To let a C function change the caller’s variable, you pass a pointer — the value of an address — and dereference it. That is still pass by value (you copied the pointer), but the copied address points back at the original. C++ adds true pass by reference with the & syntax: void change(int& x) makes x a genuine alias, so x = 99 updates the caller’s variable directly.
Why Python and Java confuse everyone
Python and Java are pass by value. The catch is what the value is: for objects, the value is a reference (an object identity / handle), and that reference is copied into the parameter. So the function and the caller now hold two separate references that point at the same object.
This produces the behavior that looks like two contradictory rules:
def demo(lst): lst.append(4) # MUTATES the shared object -> caller sees [1, 2, 3, 4] lst = [99] # REBINDS the local name only -> caller unaffected
data = [1, 2, 3]demo(data)print(data) # [1, 2, 3, 4]The append reaches through the reference and modifies the one object both names point to, so the change is visible to the caller. The assignment lst = [99] only points the local copy of the reference at a new object; the caller’s reference still points at the original list. Java behaves identically: mutate a passed object’s fields and the caller sees it; reassign the parameter and the caller does not.
This is why the precise name is pass by value of a reference, sometimes called pass by object sharing or call by sharing. It is neither classical pass by reference (because reassignment does not propagate) nor a deep copy (because mutation does propagate).
Practical consequences
This distinction is not pedantry; it predicts real behavior. A function that “clears” a list by writing lst = [] silently does nothing to the caller — you wanted lst.clear(). A function that takes a config dict and adds defaults via config["timeout"] = 30 will leak those defaults back to every caller unless you copy first. Immutable types (Python’s int, str, tuple; Java’s primitives and String) sidestep the whole issue: since you can’t mutate them, the only way to “change” one is to reassign, which never escapes the function.
The mental model to keep: assignment in these languages binds a name to an object; it never copies the object and never copies the caller’s binding. Everything else follows.
FAQ
Is Java pass by reference for objects?+
How do I actually change a caller's variable in Python?+
Does passing a large object copy all its data?+
Related reading
2026-06-04
ACID vs BASE: What Database Guarantees Actually Promise
ACID and BASE describe two ends of a tradeoff between strict correctness and scalable availability. Learn what each guarantee means, when each fits, and why most modern databases sit somewhere in between.
2026-06-04
Big-Endian vs Little-Endian
Byte order explained: how big-endian and little-endian lay out multi-byte numbers in memory, why network protocols pick one, and when the difference actually bites you.
2026-06-04
Big-O Notation in Plain English
Big-O describes how an algorithm's runtime or memory grows as input grows. Learn the common classes — O(1), O(log n), O(n), O(n log n), O(n^2), O(2^n) — with plain examples.
2026-06-04
CORS in Plain English: Why the Browser Blocks Your Fetch
A clear walkthrough of CORS and the same-origin policy — what an origin is, why your fetch fails, how servers opt in, and the big misconception about who CORS actually protects.
2026-06-04
Environment Variables and PATH, Explained
What environment variables actually are, why they hold config and secrets, and how PATH decides which binary runs when you type a command.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.