Functions — pass by value, reference & pointer

RoadmapsC++

Scenario

You write a function to modify a user's account balance. The function runs successfully, but back in main(), the balance hasn't changed.

Why did the function's modifications disappear?

Mental model

Pass-by-value is photocopying a document and giving it to someone; they can scribble on it, but your original is safe. Pass-by-reference is giving them the original document.

In C++, the default parameter passing mechanism is 'pass-by-value'—making a full copy of the argument. This is fast for small types like `int` but disastrously slow for large containers like `std::vector`. To allow modification, you pass by reference (`&`) or pointer (`*`). To avoid copying large objects without allowing modification, you pass by const reference (`const&`).

Explanation

When declaring function parameters, you have three main choices:

1. **By Value (`Type x`)**: The function gets a complete, independent copy. Any changes made inside the function do not affect the original. This is the default in C++. Use it for primitive types (`int`, `double`) and small structs.

2. **By Reference (`Type& x`)**: The function receives an alias to the original object. No copy is made. Changes inside the function directly mutate the original object. Use it when you explicitly want the function to modify the caller's variable (an 'out' parameter).

3. **By Const Reference (`const Type& x`)**: No copy is made, but the function is forbidden from modifying the object. Use this for large objects (like `std::string`, `std::vector`, or custom classes) that you only need to read.

Passing by pointer (`Type* x`) works similarly to references (it avoids a copy and allows modification), but requires explicit dereferencing and can be null. Modern C++ strongly prefers references for parameters unless null is an expected, valid input.

Code examples

Pass by value vs reference

#include <iostream>
#include <vector>

// Pass by value: makes a full copy of the vector. VERY SLOW for large inputs.
void badSum(std::vector<int> v) {
    v[0] = 99; // Modifies the copy, original is untouched
}

// Pass by const reference: no copy, fast, read-only.
void goodSum(const std::vector<int>& v) {
    // v[0] = 99; // Would not compile! (Read-only)
    std::cout << v[0] << '\n';
}

// Pass by reference: no copy, mutates original.
void modify(std::vector<int>& v) {
    v[0] = 99; // Modifies the original vector
}

int main() {
    std::vector<int> data = {1, 2, 3};
    badSum(data);
    std::cout << data[0] << '\n'; // Prints 1
    
    modify(data);
    std::cout << data[0] << '\n'; // Prints 99
    return 0;
}

`badSum` accidentally creates an O(N) copy of the entire vector on every call. `goodSum` and `modify` are O(1) in overhead because they use references.

Key points

Common mistakes

Recall questions

Questions & answers

A candidate implements a recursive DFS (Depth First Search) for a graph. Their logic is correct, but it fails with a Time Limit Exceeded (TLE) error. The signature is `void dfs(int node, std::vector<bool> visited)`. What is the issue?

The `visited` array is being passed by value. Every recursive call creates a complete copy of the array, turning an O(V + E) algorithm into an O(V * (V + E)) algorithm. It must be passed by reference `std::vector<bool>& visited`.

Approach: Recognize the performance impact of missing `&` on container parameters.

If you pass an object by value to a function, does it call the copy constructor or the assignment operator?

It calls the copy constructor. Passing by value involves initializing a new local variable with the provided argument.

Approach: Identify the mechanism of pass-by-value as object initialization, not assignment.

Continue learning

Previous: const correctness — const refs, const methods & when to use

Next: Arrays, strings & C-style vs std::string

Return to C++ Roadmap