Menu

C++ Range-Based for Loop: Syntax, auto, and References

The C++ range-based for loop explained - clean iteration over arrays, vectors, strings and maps, why you should use auto& and const auto&, and the copy and iterator-invalidation gotchas to avoid.

This page includes runnable editors - edit, run, and see output instantly.

"For Each Element, Do This"

The classic counting for loop is great when you have an index to drive. But most of the time you don't actually care about the index - you just want to touch every element of a container. Writing for (int i = 0; i < v.size(); i++) to do that is noisy, and i is one off-by-one mistake away from reading out of bounds.

C++11 added the range-based for loop for exactly this. You name a variable, point at a container, and the loop walks every element for you:

No index, no .size(), no bounds to get wrong. Read it as "for each s in scores." It works on raw arrays, std::vector, std::string, std::map, and anything else that exposes begin() and end().

Let auto Pick the Type

Spelling out the element type works, but it's fragile - change the container's type and every loop has to change too. Pair the range-based for with auto and the compiler deduces the element type for you:

There's a hidden cost here, though. Plain auto name deduces string and copies each element into name on every pass. For an int that's free; for a string or a big struct it's a wasted allocation each iteration. The fix is references, which is the next thing to understand.

Modify In Place with auto&

If you write auto x, you get a copy - so assigning to x changes the copy, not the container. Watch this trap:

The doubling silently does nothing because n is a throwaway copy. To actually edit the elements, take them by reference with auto&:

The single & is the whole difference between "look but don't touch" and "edit in place." If you ever wonder why your changes vanish, this is almost always why.

Read Without Copying: const auto&

When you only need to read elements but they're expensive to copy, use const auto&. The reference avoids the copy, and const documents (and enforces) that you won't modify anything:

A good rule of thumb:

for (auto x : c)         // copy   - cheap types (int, char, pointers)
for (auto& x : c)        // edit   - you want to change the elements
for (const auto& x : c)  // read   - heavy types you only inspect

Default to const auto& when reading and auto& when writing. Reach for plain auto only for genuinely small, cheap-to-copy types.

Looping Over Maps and Pairs

A range-based for over a std::map hands you a std::pair for each entry, with .first (the key) and .second (the value). Since C++17, structured bindings let you unpack that pair into two named variables right in the loop header:

[name, age] is far clearer than repeating entry.first and entry.second everywhere. Keep the const auto& here too - a map entry's key is a string, so copying every pair would be wasteful.

The Pitfall: Don't Resize While You Loop

The biggest gotcha is changing the container's size while a range-based for is walking it. Calling push_back, erase, insert, or clear can reallocate the underlying storage and invalidate the loop's internal iterators - the result is undefined behavior, which means crashes or garbage, not a friendly error:

vector<int> v = {1, 2, 3};
for (int x : v) {
    v.push_back(x);   // UNDEFINED BEHAVIOR - reallocation invalidates the range
}

If you need to add or remove elements while processing, switch to an index- or iterator-based for loop and manage the bounds yourself, or build a separate result container and swap it in afterward. Two smaller traps in the same family: never bind a range-based for to a temporary that dies immediately (for (auto x : makeVector()) is fine, but for (auto& x : someObj.getTempVector()) can dangle), and remember that for (auto& c : myString) lets you mutate individual characters in place.

Next: Functions

The range-based for loop tidies up iteration, and the auto / auto& / const auto& choices you just learned carry straight over to one of the most important tools in C++. Next we'll package logic into reusable functions - giving code a name, parameters, and a return value so you can call it from anywhere instead of repeating yourself.

Frequently Asked Questions

What is a range-based for loop in C++?

A range-based for loop visits every element of a container (array, vector, string, map, etc.) without you managing an index or iterator. The syntax is for (auto x : container) { ... }. It was added in C++11 and is the cleanest way to say "do this for each element."

When should I use auto& instead of auto in a range-based for loop?

Use auto& x when you want to modify the elements in place, and const auto& x when you only read them but want to avoid copying (important for string, vector, or large objects). Plain auto x makes a copy each iteration - fine for cheap types like int, wasteful otherwise.

Can you change a vector's size inside a range-based for loop in C++?

No. Calling push_back, erase, insert, or clear on the container you are iterating invalidates the loop's internal iterators and is undefined behavior - it can crash or silently corrupt data. If you need to add or remove elements while looping, use an index-based or iterator-based for loop instead.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED