Updated June 2026
A function receives an integer, doubles it, and returns. The original variable stays unchanged. That behavior catches nearly every beginner off guard the first time they expect a modification to stick. The reason: the function was working on a copy, not the original.
Pass by reference C++ solves this. Instead of copying a variable into a function, a reference parameter gives the function direct access to the original. No duplication, no wasted memory, and any changes inside the function affect the caller’s variable immediately.
This article covers how pass by reference in C++ works, how it compares with pass by value and pass by pointer, when const references are the better choice, and where references are not the right tool.
What pass by reference means in C++
Pass by reference allows a function to operate directly on the original variable passed to it, rather than on a separate copy.
In C++, this is done by declaring a parameter with the & symbol. A parameter declared as int& b is a reference parameter. It acts as an alias for the original variable. It is not a new variable and it is not a copy. It is another name for the same data.
Any modification to the reference parameter inside the function changes the caller’s variable. That is the core behavioral difference from pass by value, where the function receives its own independent copy and the original stays untouched.
A reference must be bound to a variable at the point of the function call. It cannot be null, and it cannot be reassigned to refer to a different variable later.
Arguments and parameters
These two terms come up constantly when discussing C++ function parameters, and they refer to different things.
A parameter is the variable declared in a function’s signature. It defines what the function expects to receive.
A argument is the actual value or variable passed to the function at the call site.
Using the example from this article:
void duplicate(int& b); // b is the parameter
duplicate(x); // x is the argument
When duplicate(x) is called, the argument x is bound to the reference parameter b. Inside the function, b is just another name for x.
How pass by reference works in a function
Here is the core example:
#include <iostream>
using namespace std;
void duplicate(int& b) {
b *= 2;
}
int main() {
int x = 25;
cout << "x before: " << x << endl;
duplicate(x);
cout << "x after: " << x << endl;
return 0;
}
Output:
x before: 25
x after: 50
Here is what happens step by step:
xis declared and initialized to 25.duplicate(x)is called. Because the parameter is declared asint& b, the referencebis bound tox. No copy is made.- Inside the function,
b *= 2multiplies the value by 2. Sincebis an alias forx, this modifiesxdirectly. - After the function returns, printing
xshows 50.
The key point: b is not a separate variable that happens to hold the same value. It is x, accessed through a different name. That is why the change persists after the function ends.
If the parameter were declared as int b instead of int& b, the function would receive a copy. The original x would remain 25.
When pass by reference is most useful
References are not always necessary, but there are specific situations where they solve real problems. For most everyday C++ function parameters, references are the cleaner choice when a function needs to work with the original object and null is not part of the design.
When a function needs to modify the caller’s variable
This is the most common reason to use pass by reference in C++. If a function’s purpose is to change a value owned by the caller, a reference parameter makes that intent clear.
A function can also modify caller-owned data through a pointer. But when null is not a valid input, a reference expresses the intent more directly.
Common examples include update functions, swap-style operations, and functions that accumulate results into an existing object.
When passing large objects would be expensive
Copying a small int or double is cheap. Copying a std::string, a std::vector, or a custom class instance with many member variables is not.
Passing large objects by reference avoids that copy overhead. The function works with the original data directly rather than duplicating it into a new local variable.
The actual cost depends on the type and what the compiler optimizes. Avoid blanket claims that references are always faster. But for any object larger than a few bytes, passing by reference is a reasonable default. If the function only reads the object, a const reference is usually the better signature.
When working with polymorphism
Passing a derived-class object to a function that accepts a base-class parameter by value causes object slicing. The derived-class-specific data and behavior are stripped away, and only the base-class portion is copied.
Passing by reference avoids this. The function receives an alias to the original object, preserving its full type and behavior.
void display(core& c) {
c.show();
}
When display is called with a derived-class object, the correct overridden show() function executes. This is a useful advanced case rather than the primary beginner takeaway, but it matters in any codebase that uses inheritance.
A practical example with a larger object
References are not just for integers. Consider a class that holds client data:
class ClientNumber {
public:
int id;
string name;
string address;
string phone;
};
A function that prints this object does not need to copy it:
void printNumber(ClientNumber& num) {
cout << num.name << ": " << num.phone << endl;
}
Compare this to a pass-by-value version:
void printNumber(ClientNumber num) {
cout << num.name << ": " << num.phone << endl;
}
The second version copies the entire ClientNumber object into a new local variable every time the function is called. For a small struct, the difference is negligible. For objects with strings, vectors, or other dynamically allocated members, the copy cost adds up.
If the function only reads from the object and does not modify it, the better signature is void printNumber(const ClientNumber& num). That avoids the copy and prevents accidental modification.
Pass by value vs. pass by reference vs. pass by pointer
Choosing between these three approaches depends on what the function needs to do with the data.
| Method | What the function receives | Can modify original? | Common use case | Main tradeoff |
|---|---|---|---|---|
| Pass by value | A copy | No | Small values, isolated logic | Copy overhead for large objects |
| Pass by reference | An alias to the original | Yes | Updating data, avoiding copies | Side effects if used carelessly |
| Pass by pointer | A memory address | Yes | Optional data, low-level APIs, nullable values | More complex syntax and null handling |
Pass by value protects the original variable from unintended changes. The function works on its own copy, so nothing outside the function is affected. This is the safest default for small, primitive types.
Pass by reference keeps syntax cleaner than pointers and avoids copying. It is the preferred choice in most modern C++ when the function needs to read or modify the original object and null is not a valid input.
Pass by pointer uses address-of (&) and dereference (*) syntax. It allows a function to accept “no object” by passing nullptr. This is useful in C-style APIs, optional parameters, and cases where the pointer may need to be reassigned. The tradeoff is more complex syntax and the responsibility to check for null.
For most everyday C++ function parameters, prefer references over pointers when nullability is not part of the design.
What about const references?
A const reference gives a function read-only access to the original object without copying it.
void printName(const std::string& name) {
cout << name << endl;
}
The parameter name refers directly to the caller’s string. No copy is made. But because it is declared const, the function cannot modify it. Attempting to change name inside the function results in a compile-time error.
This pattern is one of the most common in production C++. Use const references for:
- Strings passed to display or logging functions
- Vectors or containers passed to search or read operations
- Custom class instances passed to functions that only inspect data
The rule of thumb: if a function does not need to modify the input and the type is larger than a primitive, const Type& is the standard signature. A non-const reference signals to the reader that the function will change the data.
Common mistakes beginners make
Most reference-related bugs come from a small set of recurring errors:
- Forgetting the
&in the parameter declaration. The function silently receives a copy instead of a reference. The original variable stays unchanged, and the bug can be hard to spot. - Expecting pass by value to modify the original. If the parameter is not a reference, the function cannot change the caller’s data. This is the most common source of confusion.
- Thinking a reference parameter is a copy. A reference is an alias, not a duplicate. Changes to it affect the original directly.
- Using a non-const reference when the function only reads data. This misleads other developers into thinking the function modifies its input. Use
const&for read-only access. - Defaulting to pointers when references would be clearer. Pointers add null-checking complexity. If null is not a valid state, a reference is the simpler choice.
- Passing a derived-class object by value to a base-class parameter. This causes object slicing and silently strips away polymorphic behavior.
Example output comparison
Seeing the output side by side makes the behavioral difference concrete.
Given int x = 25 and a function that multiplies the parameter by 2:
- Pass by value:
xafter call = 25 (original unchanged, function modified a copy) - Pass by reference:
xafter call = 50 (original modified directly through the alias) - Pass by pointer:
xafter call = 50 (original modified through the dereferenced pointer)
Both reference and pointer approaches modify the original. The difference is syntax and intent. References use cleaner syntax. Pointers allow null and require explicit dereferencing with *.
When not to use pass by reference
References are a strong default for many situations, but not all.
- Small primitive types like
int,double, orcharare cheap to copy. Passing them by value is often just as efficient and avoids any risk of accidental modification. - Non-const references create side effects. A function that modifies its input through a reference can make code harder to reason about, especially in larger codebases. If a function does not need to change the input, pass by value or
constreference is clearer. - Functions that need to express “no object.” A reference must always be bound to a valid variable. If “nothing” is a valid input, a pointer or
std::optionalis the right tool. - Cases where the caller needs to retain an unmodified copy. Pass by value gives the function its own independent copy to work with, leaving the original untouched.
Choosing the right parameter style is about matching the function’s intent to the syntax. Not every parameter benefits from a reference.
Key takeaways
- Use pass by reference C++ when a function must modify the caller’s variable directly.
- Use references to avoid unnecessary copies of large objects like strings, vectors, and custom classes.
- Prefer
constreferences for large read-only inputs. This is the most common pattern in production C++. - Use pointers when nullability or reseating is part of the design.
- Use pass by value for small primitive types when isolation from the caller’s data is the clearer choice.
Learn C++ online
Understanding how references work is a foundational skill for writing correct, efficient C++ functions. It connects directly to memory management, function design, and real project architecture.
To build on these skills with hands-on projects covering references, memory, object-oriented design, and more, explore the C++ Nanodegree program. You can also browse the full catalog of programming and development courses to find the right fit for where you are now. Consider joining our specialized courses.



