What Is Pass by Reference vs. Pass by Value?

Pass by reference is a way of sending data to a function by giving the function direct access to the original variable, rather than handing it a separate copy. When a function receives a reference, any changes it makes affect the original data in the caller’s scope. This contrasts with pass by value, where the function works on its own independent copy and the original stays untouched.

Pass by Reference vs. Pass by Value

The distinction comes down to what the function actually receives. With pass by value, the system copies the contents of your variable into a new, separate variable inside the function. The function can do whatever it wants with that copy, and your original is unaffected. With pass by reference, the function gets a direct link to the original variable’s storage location. No copy is made, and any modification inside the function changes the original.

A simple way to think about it: pass by value is like handing someone a photocopy of a document. They can scribble on it all they want, and your original is fine. Pass by reference is like handing them the actual document. Whatever they write on it, you’ll see when you get it back.

This matters in two practical situations. First, when you want a function to modify a variable for you (like swapping two values). Second, when copying data would be expensive. Passing a large object, like a list with thousands of elements, by value means duplicating all that data in memory. Passing it by reference avoids the copy entirely. In general, the larger an object, the more passing by reference makes sense from a performance standpoint.

How It Works in Memory

When your program calls a function, it sets up a new frame on the call stack to hold that function’s local variables and parameters. With pass by value, the system copies your variable’s data into this new stack frame. The function’s parameter and your original variable occupy completely separate memory locations.

With pass by reference, the stack frame stores the memory address of your original variable instead of a copy of its data. When the function reads or writes to that parameter, it follows the address back to the original location. This is why changes “show up” in the calling code: the function was never working on its own data to begin with.

Pass by Reference in C++

C++ is the classic language for understanding pass by reference because it supports it explicitly with clear syntax. You declare a reference parameter by adding an ampersand (&) to the type in the function signature:

void swap(int& x, int& y) {
    int z = x;
    x = y;
    y = z;
}

// called as: swap(a, b);

Inside swap, x and y are not copies. They are alternative names for whatever variables you passed in. Changing x changes your original a.

C++ also lets you accomplish something similar with pointers, where you explicitly pass a variable’s memory address and dereference it inside the function:

void swap(int *x, int *y) {
    int z = *x;
    *x = *y;
    *y = z;
}

// called as: swap(&a, &b);

Both approaches let you modify the original, but references are generally preferred because they’re simpler to use. A reference can’t be null, so you’re guaranteed it points to a real variable. A pointer can be null or reassigned to point somewhere else, which adds flexibility but also more room for bugs. The common advice in C++ is to use references when you can, and pointers when you have to.

Java: Always Pass by Value

Java is officially always pass by value, which confuses a lot of people because it certainly looks like pass by reference when you work with objects. Here’s what’s actually happening.

For primitive types like int or double, Java copies the value. Straightforward. For objects, Java copies the reference (the pointer to where the object lives in memory), not the object itself. So inside a function, you have a copy of the address pointing to the same object. You can modify the object’s contents through that copied reference, and the caller will see those changes. But if you reassign the parameter to point to a completely new object, the caller’s variable still points to the old one.

This is the key distinction. In true pass by reference, reassigning the parameter inside the function would change what the caller’s variable points to. In Java, it doesn’t. Java passes the reference by value, meaning it copies the pointer but not the object. You can think of it intuitively as pass by reference for objects in most everyday situations, since modifying an object’s fields works as expected. But technically, it’s a copy of the reference.

Python: Pass by Object Reference

Python uses a model sometimes called “pass by object reference.” A variable in Python is a name attached to an object, and when you pass it to a function, the function gets a copy of that name (reference), not a copy of the object. What happens next depends on whether the object is mutable or immutable.

Mutable objects like lists and dictionaries can be changed in place. If a function appends an item to a list you passed in, your original list has that new item. The function’s reference and your reference both point to the same list object in memory.

Immutable objects like integers, strings, and tuples can’t be changed in place. If the function tries to “modify” an integer parameter (say, adds 1 to it), Python doesn’t alter the original object. Instead, it creates a new integer object and points the function’s local reference to that new object. Your original variable still points to the old one. This is why passing an integer to a function and incrementing it inside the function doesn’t change your original: the function’s copy of the reference simply got redirected to a new object.

JavaScript: Value of a Reference

JavaScript works similarly to Java. Primitives like numbers, strings, and booleans are always passed by value. Objects and arrays are passed by sharing a reference, but that reference itself is a copy.

If you pass an object to a function and the function changes one of the object’s properties, you’ll see that change in the original. But if the function reassigns the parameter to an entirely new object, your original variable is unaffected. Strictly speaking, JavaScript has no true pass by reference. Everything is passed by value; for non-primitives, the value that gets passed happens to be a reference.

C#: Explicit Reference with ref and out

C# defaults to pass by value, but it gives you an explicit opt-in for true pass by reference using the ref keyword. Both the function signature and the call site must include it:

public void AddFortyTwo(ref int number) {
    number += 42;
}

// called as: AddFortyTwo(ref myNumber);

This is true pass by reference. Reassigning number inside the function changes myNumber in the calling code. C# also has the out keyword, which works like ref but is designed for parameters that the function initializes rather than reads. The explicitness of requiring ref at both the definition and call site makes it immediately visible when a function can modify your variable.

When to Use Pass by Reference

There are two main reasons to reach for pass by reference. The first is when you need a function to modify the caller’s variable directly, such as a swap function or a function that needs to return multiple values through its parameters. Without pass by reference, you’d need workarounds like returning a new value or wrapping data in a container object.

The second reason is performance. Copying a small integer is trivial, but copying a large data structure (a vector with thousands of elements, a complex object with nested data) takes time and memory. Passing by reference avoids the overhead entirely because no duplication happens. For types like large containers, strings, or maps, passing by reference (or by pointer) is standard practice in performance-sensitive code.

The tradeoff is safety. When you pass by reference, any function you call can potentially change your data. If you want the performance benefit without the risk, many languages let you pass a read-only reference. In C++, this looks like const int&, which gives the function access without permission to modify. This pattern is extremely common for function parameters that just need to read large objects.