Pass by value vs pointer vs reference in C++
Short answer: pass by value gives the function a copy (changes don't escape). Pointers and references both give it the real object (changes escape). A pointer can be null and can move; a reference is an alias that's bound forever. Default to value for small things, const reference for big read-only things.
Watch the difference happen — both functions try to set their argument to zero, and only one succeeds:
#include <iostream>
using namespace std;
void tryToZero(int x) { x = 0; } // a copy — caller never sees it
void actuallyZero(int* x) { *x = 0; } // the real object's address
int main() {
int a = 5, b = 5;
tryToZero(a);
cout << "after tryToZero: " << a << endl;
actuallyZero(&b);
cout << "after actuallyZero: " << b << endl;
return 0;
}
The demos on this page use pointers because our in-browser engine doesn't support reference parameters (int&) yet — but every rule below covers references too, and we'll show their syntax in code blocks.
The three options, side by side
| by value | by pointer | by reference | |
|---|---|---|---|
| Syntax | f(int x) | f(int* x), call f(&a) | f(int& x), call f(a) |
| What arrives | A copy | The object's address | An alias for the object |
| Caller sees changes? | No | Yes | Yes |
| Can be "nothing"? | No | Yes — null pointer | No — must be bound |
| Copy cost | Full copy every call | Just an address | Just an alias |
The classic use case: swapping two variables
A swap must modify the caller's variables, so copies are useless — you need addresses:
#include <iostream>
using namespace std;
void mySwap(int* a, int* b) {
int t = *a; // save the value a points to
*a = *b;
*b = t;
}
int main() {
int x = 1, y = 2;
mySwap(&x, &y);
cout << x << " " << y << endl; // 2 1
return 0;
}
The reference version reads more cleanly at the call site — same mechanics, no &/* noise:
void mySwap(int& a, int& b) {
int t = a;
a = b;
b = t;
}
// call: mySwap(x, y); // looks like by-value, acts like a pointer
Surprise: arrays are always passed like pointers
When you pass a C-style array, what actually arrives is a pointer to its first element — the size never travels with it. That's why every function taking an array takes a length too:
#include <iostream>
using namespace std;
int sum(int arr[], int n) { // arr is really int*
int s = 0;
for (int i = 0; i < n; i++) s += arr[i];
return s;
}
int main() {
int a[3] = {10, 20, 30};
cout << sum(a, 3) << endl;
return 0;
}
So which one do I use?
- Small, simple values you just read (
int,double,char): pass by value. Copies are free, and the function can't break your variables. - Big objects you only read (a long string, a large vector): pass by
constreference —const std::string& text. No copy, and the caller's object is protected. - The function must change the caller's variable: reference (or pointer). Reference reads better; pointers are the C-era way.
- "Might not have an object" is a real possibility: pointer, and check for null before using it. A reference can't be null — that's its superpower and its limitation.
- C-style arrays: they decay to pointers whether you like it or not — always pass the length alongside.
Interview follow-ups worth having ready
- "Pointer vs reference — give me one concrete reason for each." Pointer: it can be null / can be reseated. Reference: it must always refer to something and never needs
*checks. - "Why pass big objects by const reference?" — avoiding the copy dominates;
constguarantees read-only. - "What's wrong with
int& f()returning a reference to a local?" — the local dies when the function returns; the reference outlives its object. Undefined behavior, and a favorite trick question. - "Does pass by value always cost a copy?" — since C++11, move semantics can turn expensive copies of temporaries into cheap moves.
Practice in the playground
- Write
addOne(int* p)that increments the caller's variable; call it onxand print. - Change
mySwapto takeint*by value — i.e. swap only the pointers inside the function — and explain to yourself why the caller sees no change. - Write
maxOf(int a, int b, int c)by value and confirm small-value passing is the right default.
Related: 15 beginner interview questions (pointers vs references is Q1 there), ++i vs i++, and integer division.