Pass-by-Value vs. Pass-by-Reference vs. Pass-by-Sharing
Core Fundamentals
How is Data Passed to Functions?
When you pass an argument to a function, the programming language needs a mechanism to get that data into the function's scope. This mechanism is called the parameter passing strategy. Understanding this is critical because it determines whether a function can modify the original data you pass to it.
This is one of the most commonly misunderstood topics in programming, and clarifying it in an interview shows a deep level of understanding. The three main models are:
- Pass-by-Value
- Pass-by-Reference
- Pass-by-Sharing (The model used by Python, Java, JavaScript, Ruby, and others)
1. Pass-by-Value
In a pass-by-value system, when a function is called, it receives a copy of the argument's value. The function's parameter is a new variable in memory, initialized with the value of the argument.
Key Implication: The function cannot change the original variable outside its scope. Any modifications are made to the local copy only.
- Languages: C, C++, C#, Go, Swift (for value types like structs and enums).
#include <iostream>
// The 'num' parameter is a copy of the 'original_number' argument.
void increment(int num) {
num = num + 1;
std::cout << "Value inside function: " << num << std::endl; // Prints 11
}
int main() {
int original_number = 10;
increment(original_number);
std::cout << "Value outside function: " << original_number << std::endl; // Prints 10
return 0;
}
Explanation: The increment
function receives a copy of original_number
. When it modifies num
, it's only changing its local copy. The original_number
remains untouched.
2. Pass-by-Reference
In a pass-by-reference system, the function receives a reference (or an alias) to the original variable. It does not get a copy of the value; it gets the memory address of the original data.
Key Implication: The function can modify the original variable because the parameter is just another name for the same piece of data in memory.
- Languages: C++ (using
&
), Swift (usinginout
), Pascal.
#include <iostream>
// The '&' makes 'num' a reference to the original variable.
void increment(int &num) {
num = num + 1;
std::cout << "Value inside function: " << num << std::endl; // Prints 11
}
int main() {
int original_number = 10;
increment(original_number);
std::cout << "Value outside function: " << original_number << std::endl; // Prints 11
return 0;
}
Explanation: The increment
function receives a direct link to original_number
. When it modifies num
, it is directly modifying original_number
in the main
function's scope.
3. Pass-by-Sharing (The "Python/Java Model")
This is the model that causes the most confusion. It's often incorrectly called "pass-by-reference." In pass-by-sharing (also known as pass-by-object-reference or pass-by-assignment), the function receives a copy of the reference to the object.
Let's break that down:
- The variable itself is not an object; it's a reference (a pointer) to an object in memory.
- When you pass this variable to a function, the function gets a copy of that reference.
- Both the original reference (outside the function) and the copied reference (inside the function) point to the exact same object in memory.
Key Implications:
- You can mutate the object: If the object is mutable (like a list in Python or an object in JavaScript), the function can change the object's internal state, and the changes will be visible outside the function.
- You cannot reassign the original variable: If you try to reassign the parameter to a completely new object inside the function, it only affects the function's local copy of the reference. The original variable outside the function remains unchanged.
- Languages: Python, Java, JavaScript, Ruby, C# (for reference types like classes).
Example: Mutating the Object (Visible Change)
# Python: Lists are mutable
def add_item(my_list):
# 'my_list' is a copy of the reference, but it points to the same list object.
my_list.append(4) # We are MUTATING the original object.
data = [1, 2, 3]
add_item(data)
print(data) # Output: [1, 2, 3, 4]
// JavaScript: Objects are mutable
function addProperty(obj) {
// 'obj' points to the same object as 'person'.
obj.city = "New York"; // MUTATING the original object.
}
let person = { name: "Alice" };
addProperty(person);
console.log(person); // Output: { name: "Alice", city: "New York" }
// Java: ArrayLists are mutable
import java.util.ArrayList;
class Main {
public static void addItem(ArrayList<Integer> list) {
// 'list' points to the same ArrayList object.
list.add(4); // MUTATING the original object.
}
public static void main(String[] args) {
ArrayList<Integer> data = new ArrayList<>();
data.add(1); data.add(2); data.add(3);
addItem(data);
System.out.println(data); // Output: [1, 2, 3, 4]
}
}
Example: Reassigning the Reference (No Visible Change)
# Python
def reassign_list(my_list):
# This creates a NEW list and makes the local 'my_list' reference point to it.
# The original 'data' variable is unaffected.
my_list = [10, 20, 30]
data = [1, 2, 3]
reassign_list(data)
print(data) # Output: [1, 2, 3]
// JavaScript
function reassign_object(obj) {
// This creates a NEW object. The original 'person' variable is unaffected.
obj = { name: "Bob" };
}
let person = { name: "Alice" };
reassign_object(person);
console.log(person); // Output: { name: "Alice" }
// Java
import java.util.ArrayList;
class Main {
public static void reassignList(ArrayList<Integer> list) {
// This creates a NEW ArrayList. The original 'data' variable is unaffected.
list = new ArrayList<>();
list.add(10);
}
public static void main(String[] args) {
ArrayList<Integer> data = new ArrayList<>();
data.add(1);
reassignList(data);
System.out.println(data); // Output: [1]
}
}
Summary for Interviews
- Pass-by-Value: Function gets a copy of the value. Cannot change the original. (C, C++)
- Pass-by-Reference: Function gets a reference to the original variable. Can change the original. (C++ with
&
) - Pass-by-Sharing: Function gets a copy of the reference.
- Can change the internal state of a mutable object.
- Cannot reassign the original variable to a new object.
- This is how most modern, high-level languages work (Python, Java, JS, Ruby).
When asked "Is Python pass-by-value or pass-by-reference?", the most accurate answer is: "It's neither, it's pass-by-sharing. This means that for mutable types like lists, it can feel like pass-by-reference because you can modify the object's contents. However, you can't reassign the original variable, which shows it's not true pass-by-reference."