Range-Based for Loops

Most of the time when you loop over a collection, you don't actually care about the index — you just want to touch every element. The range-based for loop says exactly that, with far less to get wrong: no i < size() , no ++i , no off-by-one bugs. By the end of this lesson you'll iterate vectors, strings, and maps cleanly, and know precisely when to use a reference.

Learn Range-Based for Loops in our free C++ course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

A classic indexed loop is like reading a book by page number : "go to page 1, read it, go to page 2…" — you have to track the page count and not run past the end. A range-based loop is like flipping through the book page by page : you just say "for each page, read it", and the book takes care of where it starts and stops. You give up knowing the page number, but you gain simplicity and you can never fall off the end.

Rule of thumb: const auto& to read, auto& to write, plain auto only for tiny types.

1. The Basic Range-Based for

The syntax is for (Type element : container) . On each pass, element takes the value of the next item in the container, and the loop ends automatically when there are no more. There's no index, no bounds check, no increment to write — which removes a whole category of off-by-one bugs. Compare it with the equivalent index-based loop in the example.

2. Value vs Reference — the Key Decision

The one thing you must get right is the loop variable's form. A plain value ( int n or auto n ) iterates over copies , so writing to it doesn't change the container. To modify elements, use auto& . To read large objects (like strings) efficiently without copying, use const auto& . This single choice is the most common source of "my loop didn't change anything" confusion.

Your turn. The loop should add 5 to every score and have it stick. Fill in the blank:

These lines sum a vector with a range-based loop. Put them in the right order:

Open main (E), declare the vector (C), initialize sum to 0 (B), accumulate over every element (A), print the result 10 (D), then return 0; (F) and close the brace (G). sum must exist before the loop adds to it.

3. Strings, Maps & Structured Bindings

Anything with begin() and end() is iterable, so the range-based loop works on far more than vectors. A std::string is a range of char . A std::map yields key/value pairs, which you can unpack directly in the loop header with structured bindings : for (const auto& [key, value] : myMap) . This is the cleanest way to walk a map.

4. Arrays & Initializer Lists

You can range directly over a braced list like {' '} and over plain C-style arrays — a built-in array knows its own length, so no sizeof arithmetic is needed. This makes quick aggregations (sum, max, count) genuinely tidy.

A range-based loop is "syntactic sugar". The compiler rewrites for (auto x : c) into roughly the iterator loop below — which is exactly why it works on anything with begin() and end() :

This also explains the danger of resizing inside the loop: operations like push_back can reallocate a vector and invalidate __it and __end , leading to undefined behaviour. If you must add or remove elements, use a classic loop or build a new container.

Predict the output before revealing the answer.

1 2 3 — the first loop modifies copies, not the vector. You'd need auto& for the change to stick.

30 — you can range over an initializer list directly: 5 + 10 + 15 = 30.

3 — the string "C++" has three characters: 'C', '+', '+'.

One pass, two results: total all prices and count the pricey ones — all with a single range-based loop.

Practice quiz

What is the syntax of a range-based for loop?

  • for (Type element : container)
  • for (int i = 0; i < n; ++i)
  • for each (element in container)
  • foreach (container as element)

Answer: for (Type element : container). The form is for (Type element : container), which visits each element directly with no index.

What does this print? vector<int> v = {1,2,3}; for (int x : v) x += 10; for (int x : v) cout << x << ' ';

  • 11 12 13
  • 1 2 3
  • 10 20 30
  • nothing

Answer: 1 2 3. A plain value loop variable iterates over copies, so x += 10 doesn't change the vector. It prints 1 2 3.

Which loop form lets you modify each element of a container in place?

  • for (auto x : c)
  • for (const auto& x : c)
  • for (auto& x : c)
  • for (int x : c)

Answer: for (auto& x : c). auto& binds a reference to each element, so writing to it changes the real container element.

What prints? int sum = 0; for (int x : {5, 10, 15}) sum += x; cout << sum;

  • 0
  • 15
  • 30
  • It does not compile

Answer: 30. You can range over a braced initializer list directly: 5 + 10 + 15 = 30.

How do you cleanly iterate a std::map in a range-based for loop?

A map yields key/value pairs; structured bindings [key, value] unpack each pair in the loop header.

Is it safe to call push_back or erase on a container while ranging over it with a range-based for?

  • Yes, the loop adjusts automatically
  • No, resizing can invalidate the internal iterators and cause undefined behavior
  • Yes, but only for std::vector
  • Only if you also keep an index

Answer: No, resizing can invalidate the internal iterators and cause undefined behavior. Operations that resize the container can invalidate begin()/end(), leading to undefined behavior. Apply changes after the loop.

Can you get the current index inside a range-based for loop directly?

  • Yes, via the built-in index variable
  • No — you keep a separate counter or use a classic indexed loop
  • Yes, by calling element.index()
  • Only on std::array

Answer: No — you keep a separate counter or use a classic indexed loop. The range-based loop does not expose an index; if you need one, keep a counter or use a classic for loop.

How many characters does this loop visit? int n = 0; for (char c : string("C++")) n++; cout << n;

  • 2
  • 3
  • 4
  • 1

Answer: 3. A std::string is a range of chars, and "C++" has three characters: 'C', '+', '+'.

Why prefer for (const auto& s : words) over for (string s : words) for a vector of strings?

  • It sorts the strings first
  • It avoids copying each string while reading them
  • It makes the loop run in parallel
  • It allows modifying each string

Answer: It avoids copying each string while reading them. const auto& binds a reference, so no copy of each (potentially large) string is made; plain value copies every one.

Which of these can a range-based for loop iterate over?

  • Only std::vector
  • Only containers with an index operator
  • Anything with begin() and end(), including arrays and initializer lists
  • Only std::map and std::set

Answer: Anything with begin() and end(), including arrays and initializer lists. The loop is sugar over begin()/end(), so it works on vectors, strings, C-arrays, maps, and braced lists.