Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Writing Efficient Interview Functions with Modern C++ Semantics

Hello! In the previous lesson, you used sort, reverse, and lower_bound on vectors. Those calls work on the vector you give them. When you put such code inside an interview function, the next question is: should the function receive the caller’s vector itself, a read-only reference to it, or its own copy?

This lesson connects that choice to range-based loops. You will learn how to express “read,” “modify,” and “work on a separate value” in C++ without copying a vector—or every element in it—by accident.


Let the parameter express the function’s intent

A function parameter can receive an argument by value or by reference. With a value parameter, the function has its own object. With a reference parameter, it works with the caller’s object. Adding const to a reference lets the function read that object but prevents it from changing it through that reference.

Watch C++ Const Reference by Kenny Yip Coding for the distinction in both function parameters and loops.

C++ Const Reference

Watch how a const reference avoids copying a vector while expressing that the function only reads it.

Watch function parameters for the contrast between cheap scalar values and larger containers. Then watch loop elements for the same choice applied to individual strings and integers.

Consider a function that counts qualifying elements:

#include <vector>

int countAtLeast(const std::vector<int>& values, int threshold) {
    int count = 0;
    for (int value : values) {
        if (value >= threshold) {
            ++count;
        }
    }
    return count;
}

The two parameters make different promises. threshold is an int: making a copy of one integer is cheap, so passing it by value is appropriate. values might contain thousands of integers. const std::vector<int>& avoids copying the vector and prevents the function from changing its elements or size through that parameter.

For example, countAtLeast({3, 6, 8}, 6) returns 2. The function reads the vector; it does not need an independent vector of its own. Writing std::vector<int> values instead would copy a caller’s vector when one is passed as an existing object. Copying all elements takes time and additional storage proportional to , before the counting loop even begins. Writing const std::vector<int> values would still make that copy: const would merely prevent changes to the local copy.

Read the focused portions of Learn C++’s 12.6 — Pass by const lvalue reference for the reasoning behind this convention.

12.6 — Pass by const lvalue reference – Learn C++

Learn C++ explains what a const reference permits and gives a practical rule for choosing between value and reference parameters.

In “12.6 — Pass by const lvalue reference,” start with the opening explanation of which arguments a const reference can accept. Read the explanation and example, stopping after the first “Best practice” note. Then find “When to use pass by value vs pass by reference” and read the rule of thumb. You can stop before the subsequent lists and “Advanced” discussion.

The rule is a starting point, not a rule that all class types must use references. For the interview functions here, it leads to a useful default: pass an int or char by value; pass a vector or string that you only need to read by const reference. Use a non-const reference, such as std::vector<int>&, only when changing the caller’s vector is part of the function’s contract.


Choose a value when you need an independent result

Avoiding copies is not the only goal. Sometimes a separate value is exactly what the problem asks you to produce.

Suppose an interview prompt asks for a sorted copy while leaving the input unchanged. A value parameter provides a vector that the function is free to sort:

#include <algorithm>
#include <vector>

std::vector<int> sortedCopy(std::vector<int> values) {
    std::sort(values.begin(), values.end());
    return values;
}

With an existing caller vector, sortedCopy(original) gives the function its own vector. sort rearranges that local vector, and the function returns the result by value. If original was {3, 1, 2}, it remains {3, 1, 2}; the returned vector is {1, 2, 3}. Returning a vector by value is a normal C++ pattern—do not return a reference to this local values, which ceases to exist when the function finishes.

Contrast that with an in-place sort:

void sortInPlace(std::vector<int>& values) {
    std::sort(values.begin(), values.end());
}

Here the non-const reference tells the caller that their vector can change. A const reference would prevent the sort, while a value parameter would sort a separate vector and leave the caller’s vector unchanged. The right signature follows from the requested behavior, not from a blanket preference for references.


Make the same choice for each loop element

A range-based loop visits the elements of a range without requiring you to manage an index. Its declaration still determines whether each loop variable is a copy or a reference.

Read the example in Microsoft Learn’s Range-based for Statement (C++). It shows the three forms you will use most often.

Range-based for Statement (C++) - Microsoft Learn

Microsoft Learn puts by-value, mutable-reference, and const-reference loops side by side, making their differences easy to compare.

In “Remarks,” find the code example introduced by “This code shows how to use range-based for loops to iterate through an array and a vector.” Read the code example through the line printing “end of vector test.” Focus on auto y, auto &y, and const auto &y, then the final vector loop. One comment beside auto y : x says it copies x; more precisely, each iteration copies one element, not the entire array.

The forms are worth reading as declarations of intent:

Loop declaration What the loop variable represents Appropriate use
auto value A copy of the current element Reading a cheap element such as an int
const auto& value A read-only reference to the current element Reading an element that may be costly to copy, such as a std::string
auto& value A modifiable reference to the current element Changing elements in the original container

That explains the int value in countAtLeast: copying one integer per iteration is reasonable. For strings, copying every element merely to inspect it is usually unnecessary:

#include <string>
#include <vector>

std::size_t totalCharacters(const std::vector<std::string>& words) {
    std::size_t total = 0;
    for (const std::string& word : words) {
        total += word.size();
    }
    return total;
}

The parameter’s const reference avoids copying the vector. The loop’s const reference avoids copying each string. Both choices matter. const auto& word would have the same effect here if you prefer the compiler to deduce the element type.

When the task is to change elements, use a mutable reference at both levels:

void addOneToAll(std::vector<int>& values) {
    for (int& value : values) {
        ++value;
    }
}

Calling this function on {3, 1, 2} changes that vector to {4, 2, 3}. If the loop instead said for (int value : values), ++value would change only the current copy; the vector would stay the same. Conversely, if the parameter were const std::vector<int>&, changing an element through int& would not be allowed.

Range-based loops are a good fit when you need each element but not its position. If the problem requires an index—perhaps the output must report where a value occurs—use an indexed loop rather than trying to recover the index from a range-loop variable.


Read a signature as a contract

Before writing a loop in an interview function, ask two separate questions:

  1. What may the function do to the caller’s argument? Use const std::vector<T>& to read it without a copy, std::vector<T>& to modify it, or std::vector<T> when the function needs its own value. Use value parameters for cheap scalars such as int.
  2. What may the loop do to each element? Use a value for a cheap copy, a const reference to inspect without copying, or a mutable reference to edit the original element.

These decisions are independent. A function can receive a vector by const reference and loop over its integers by value, as countAtLeast does. It can also receive a vector by value and modify that local vector before returning it, as sortedCopy does. The important thing is that the parameter and loop declarations match the behavior you intend.


Takeaways

Use const references for large inputs you only read, values for cheap scalars or an intentionally independent object, and mutable references when the caller’s object must change. Apply the same reasoning inside range-based loops: int value is fine for integer reads, const auto& value avoids copying larger elements, and auto& value edits elements in place.

Next, you will analyze the time and auxiliary-space cost of iterative C++ code. The copies a function or loop makes are part of that analysis, even when they are not written as explicit loops.

Can't find a good explanation? Sign up and we'll make it for you

Sign up