AlgoMaster Logo

References Basics

High Priority26 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A reference in C++ is another name for an existing variable. Once the alias is set up, reading or writing through the reference reads or writes the original. This chapter treats references as a first-class language feature, with their own declaration rules, lifetime details, and the const form that appears in most well-written C++ code.

What a Reference Is

A reference is an alias. The statement int& r = stock; does not create a new int. It gives the existing variable stock a second name. From that point on, r and stock are two labels for the same memory cell. Reading r reads stock. Writing r writes stock. There is no separate object behind r to copy values into.

Three rounds of reading and writing, and stock and alias always agree. They are the same int. Writing through one is visible when reading the other, in either direction.

A diagram: a single memory cell with two labels stuck on it.

There is one int in memory. stock is one name for it. alias is another. The arrow on either side means "this name refers to this memory." Neither name has its own storage; both resolve to the same cell.

This is different from copying. With int copy = stock;, the result is a new int that starts with the same value. Later writes to copy do not touch stock. With a reference, there is no second int at all.

A reference adds no runtime size to the object it aliases. Compilers typically implement references as pointers, but the language model is "another name for the same object," and that is the model to reason with.

Declaration Syntax

The syntax for declaring a reference is T& name = existing;. The & is part of the type, not the name. It tells the compiler "this is a reference to T," and the initializer says which existing object to refer to.

Three references, three different types. Each one is initialized at the point of declaration, and each one points at a named variable.

A reference must be initialized at the moment it is declared. There is no "default-constructed reference" and no "uninitialized reference" in C++. The compiler rejects code that tries to declare a reference without an initializer:

g++ rejects this with:

That rule is the reference's main safety guarantee. Because every reference must be tied to an existing object at birth, there is no equivalent of a "null reference" in well-formed C++ code. (Pointers can be nullptr; references cannot.)

A second rule: once a reference is bound, it cannot be made to refer to a different object. This is the "no rebinding" rule, and it confuses programmers coming from languages where reference assignment changes what the reference points at.

The line r = stockB; looks like it might say "make r refer to stockB instead." It does not. Because r is already bound to stockA, that line means "read stockB's value and write it into stockA through r." After the assignment, stockA is 99, and r still refers to stockA.

Once a reference is bound, every later use of its name is a use of the original variable. There is no syntax in C++ for rebinding a reference. The construct that allows rebinding is a pointer.

The diagram for "no rebinding":

r is fixed to stockA from the moment of declaration. stockB is its own separate cell. The line r = stockB; copies stockB's value into stockA, but the arrow from r never moves.

Modifying Through the Alias

Because a reference and its original are the same object, any change to one is visible through the other. This property makes references useful for in-place modification, especially when one function hands a variable to another piece of code that has to update it.

A small e-commerce example: a stock counter that gets adjusted as orders come in.

Every -= and += through currentStock lands directly on stock. There is no copy step, no "write the result back" pass. The two names refer to the same int, so the math touches the original.

This becomes more useful when the reference is a parameter and the modifying code lives in a different function. The function signature uses T&, and the body modifies the parameter as if it were the caller's variable, because it is.

stock inside recordOrder is an alias for whatever the caller passed. The two calls subtract 3 and then 7, both directly on productStock. The function does not return anything because the change happens through the alias.

sizeof and the Type System

A common question after learning about references: "if a reference is implemented as a pointer, does that not make it eight bytes?" The implementation detail is often true. The language-level answer is no. In the C++ type system, a reference is not a separate object with its own storage; it is an alias for an existing object, and sizeof(T&) is sizeof(T).

On a typical 64-bit system, int is four bytes, and sizeof(int&) reports four as well. The compiler is asking "how big is the object this alias refers to?", and the answer is "the same as the underlying int." The pointer-shaped machinery the compiler may use internally is invisible to the type system.

This matters in one practical way: references cannot be put into arrays. int& arr[3]; is a compile error, because the language says references are not first-class objects that can be grouped into an array. An array of "things that refer to other things" is an array of pointers.

const References

A const reference is a read-only alias. The function or block that holds the reference can read the underlying object through it, but cannot write to it through that reference. This is the standard idiom for "look at this object without copying it, and promise not to change it."

The declaration is const T& name = existing;. The const sits in front of the type.

nameView reads the string fine. The commented-out line would be a compile error: assignment through a const reference is not allowed.

Uncommenting that line, g++ reports:

The compiler enforces the promise. The reference says "read-only," and the compiler holds the code to it.

const T& is the standard parameter type for any non-trivial value that only needs to be read. It passes a single address (no copy of the underlying object), and the const blocks accidental modification.

The common place const T& appears is in function parameters for std::string, std::vector, and user-defined classes.

The first call passes an existing std::string. The second passes a string literal, which gets converted to a temporary std::string. Both work, because const std::string& can bind to both an existing variable and a temporary. A non-const reference cannot bind to the temporary; that case is covered below.

References as Function Parameters (Brief Recap)

A refresher on references as parameters, since the rest of this chapter assumes the shape.

  • T& lets a function modify the caller's variable. Pick it when modification is the goal.
  • const T& lets a function read the caller's object without copying. Pick it when reading non-trivial types.
  • T (by value) gives the function its own copy. Pick it for small built-in types where a copy is cheap.

The reference form is invisible at the call site: recordOrder(stock, 3) looks the same whether stock is passed by value or by reference. The function's signature is the only place to find out which one is happening. That is a trade-off, but the function name usually makes the intent clear (a function called record or apply mutates; one called print or compute reads).

A small e-commerce illustration combining both forms:

addItem mutates the cart, so its parameter is std::vector<double>&. computeTotal only reads the cart, so its parameter is const std::vector<double>&. printGreeting only reads the name, so its parameter is const std::string&. Three different intents, three different parameter types, and none of them copy the cart or the string.

References as Return Values

A function can return a reference, not just take one. Container types like std::vector use this to provide a way to read and write an element using [] syntax.

The line stock[1] = 99; works because std::vector::operator[] returns a reference to the element, not a copy. The expression stock[1] is an alias for the second slot in the vector's internal array, and the assignment writes through that alias.

A function can be written in the same style. A small e-commerce shopping cart class with a getCart() method that returns a reference, so the caller can modify the underlying vector without going through wrapper methods for every operation:

getCart() returns std::vector<double>&, so cart.getCart().push_back(19.99) modifies the cart's internal vector directly. The second getCart() const overload returns const std::vector<double>&, which the compiler picks when the cart is itself const (like view in the example). That overload lets read-only callers iterate over the items without being able to mutate the cart.

Returning a reference to data that the function owns is dangerous. The classic mistake is returning a reference to a local variable:

What is wrong with this code?

name is a local variable. It lives in the function's stack frame, and that frame is destroyed when the function returns. The returned reference points at memory that no longer holds a string. Reading through that reference back in the caller is undefined behavior, often manifesting as garbage characters or a crash. This pattern is called a dangling reference.

g++ warns about the simple cases:

The rule: only return a reference to an object that outlives the function call. That includes:

  • Objects owned by a parameter (the cart example above: getCart() returns a reference to items, which is a member of an object the caller already owns).
  • Static or global variables.
  • Heap-allocated objects whose lifetime the caller manages.

Returning a reference to a local stack variable is always a bug.

Binding a const Reference to a Literal

A non-const reference must bind to an existing, named object. A const reference is more permissive: it can also bind to a literal or other temporary, and the temporary's lifetime is extended to match the reference's lifetime.

The literal 42 does not have a permanent home on its own. It would be a temporary that exists for the duration of one expression. Binding it to a const int& extends its life to match r. Inside the block where r is defined, the temporary stays alive, and it can be read through r repeatedly.

The same rule applies to function-call temporaries:

fullName() returns a temporary std::string. Binding it to a const std::string& keeps the temporary alive for the rest of the block, so the std::cout line sees a valid string.

This same rule is what makes const T& parameters so flexible. A function declared as void f(const std::string& s); can be called with a variable, a literal, a temporary, or any expression that produces a std::string. A non-const std::string& parameter only accepts an existing, named std::string.

readOnlyOk accepts both calls because const std::string& binds to temporaries. mutableNotOk rejects the literal call, because a non-const reference cannot bind to a temporary. If that line is uncommented, g++ reports:

There is a reason for that rule. If mutableNotOk were allowed to modify the temporary, the modification would have no observable effect (the temporary is destroyed at the end of the expression). The compiler refuses code that looks like it modifies something but does not.

Common Mistakes

References have a small set of pitfalls. Knowing the shape of each one makes them easier to avoid.

Mistake 1: Trying to Declare a Reference Without an Initializer

A reference must be initialized at the point of declaration. Forgetting the initializer is a compile error.

The fix is to provide the variable the reference should alias:

There is no equivalent of "default-constructed reference" or "null reference" in C++. For a thing that might or might not refer to an object, use a pointer, not a reference.

Mistake 2: Thinking = Rebinds a Reference

This is a common conceptual mistake about references, especially for programmers used to languages where reference assignment changes the target.

After r = b, r still refers to a. The line r = b is the same as a = b. It copied b's value (2) into a. There is no syntax in C++ for rebinding a reference. For a thing whose target can change over time, use a pointer.

Mistake 3: Returning a Reference to a Local Variable

A function's local variables live on its stack frame, which is destroyed when the function returns. A reference to a local is dangling the moment the function exits.

The caller gets a reference to memory that no longer holds a string. Reading it is undefined behavior. The fix is to return by value:

Return by value lets the caller keep the string after the function exits. Modern C++ optimizes this so that no extra copy happens for the common cases. The dangling pointers lesson covers this in more detail.

Mistake 4: Modifying Through a const Reference

A const T& is read-only. Writing through it is a compile error.

If the function should modify the caller's string, drop the const. If it should compute and return a new value without touching the original, return a new string:

Returning a new value is usually the clearer shape. It keeps the input read-only and makes the output explicit.

Mistake 5: Holding a const Reference Past a Temporary's Lifetime

Binding a const reference to a temporary extends the temporary's lifetime to match the reference, but only when the reference is a named local variable. If the reference is a function parameter or a returned reference, the lifetime extension does not carry through.

The function returns a reference that already dangles. The temporary std::string("Ashish") was kept alive by name only inside the function. As soon as the function returns, the temporary is destroyed, and any caller who reads through the returned reference is reading dead memory.

The fix, again, is to return by value:

The full lifetime-extension rules are covered in the dangling pointers lesson. For now, the rule of thumb is: binding a const T& to a temporary in a local declaration is safe, but be careful about returning references out of a function.

Quiz

References Quiz

10 quizzes