C++ Lesson 51 – Range-Based Loops | Dataplexa

Modern C++ Β· Lesson 51

Range-Based Loops

For years, looping over an array meant counting positions: start at 0, stop before the size, add 1 each time. It works, but it is a lot of bookkeeping for a simple job. C++11 gave us a shorter, safer way: the range-based for loop. You just say "for each item in this collection, do this" β€” and the loop handles all the counting for you.

1. What is a range-based for loop?

A range-based for loop visits every element of a container β€” an array, a vector, a string, a map β€” one by one, from first to last. You do not write an index, you do not write a stop condition, and you cannot run past the end by mistake. The loop knows exactly where the container ends.

Think of a teacher calling the roll: instead of saying "student number 1, student number 2, student number 3", she simply walks down the class list and names each student in turn. That is exactly what this loop does with your data β€” it takes each element in turn and hands it to you.

2. The basic syntax

The syntax is short: for (auto item : collection). The auto lets the compiler work out the element type, and the colon reads as "in". So for (auto score : scores) means "for each score in scores".

Code story: A teacher has four test scores stored in a vector. She wants to print every score on one line. The old way needs an index and the size of the vector; the new way just says "for each score in scores, print it". Watch how the loop visits 85, then 92, then 78, then 95 β€” in order, with no counting code at all.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> scores = {85, 92, 78, 95};
    for (auto score : scores) {
        cout << score << " ";
    }
    cout << endl;
    return 0;
}
85 92 78 95

What just happened? The loop ran four times β€” once per element. Each time, score held a copy of the next element, and we printed it. There is no index variable, no .size() call, and no chance of reading past the end of the vector.

Here is the same idea side by side with the old loop, so you can see how much simpler the new one is:

Loop styleCodeWhat it does
Old indexed loopfor (int i = 0; i < n; i++)Counts positions by hand; you manage the index and the stop condition.
Range-based loopfor (auto x : scores)Visits every element in order; no index, no size, no off-by-one mistakes.
Range-based loop with referencefor (auto& x : scores)Visits the real elements, so you can change them as you go.

3. Changing elements with auto&

Plain auto x gives you a copy of each element. Changing the copy changes nothing in the container β€” the original stays exactly as it was. If you want to change the real elements, use a reference: write auto& x. The & turns the loop variable into a nickname for the real element, so anything you do to it happens to the container itself.

Code story: A shop wants to double every price in its list for a sale. First the program prints the old prices, then it loops with auto& and doubles each real price, then it prints the list again. Watch the second printout: every price is doubled, because the loop touched the real elements, not copies.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> prices = {10, 20, 30};
    cout << "Before: ";
    for (auto price : prices) {
        cout << price << " ";
    }
    cout << endl;
    for (auto& price : prices) {
        price = price * 2;
    }
    cout << "After: ";
    for (auto price : prices) {
        cout << price << " ";
    }
    cout << endl;
    return 0;
}
Before: 10 20 30
After: 20 40 60

What just happened? The first loop only printed the prices. The second loop used auto&, so price was the real element each time β€” doubling it doubled the value stored in the vector. The third loop printed the changed list to prove it.

Code story: Now watch what happens when the same job is tried with plain auto instead of auto&. The loop doubles each copy and throws it away β€” the prices in the vector never change. The final printout proves it: the list is exactly the same as before.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> prices = {10, 20, 30};
    for (auto price : prices) {
        price = price * 2;   // changes only the copy
    }
    cout << "After trying with auto: ";
    for (auto price : prices) {
        cout << price << " ";
    }
    cout << endl;
    return 0;
}
After trying with auto: 10 20 30

What just happened? With plain auto, each price is a separate copy. Doubling the copy does nothing to the vector, so the printout shows the original prices 10, 20 and 30. This is the most common beginner mistake with range-based loops β€” and now you know how to spot it.

Rule of thumb: Use const auto& when you only read, auto& when you must change, and plain auto only for tiny values like int or double.

4. Reading safely with const auto&

When you only need to read the elements β€” especially big ones like strings β€” the best choice is const auto&. The & means no copy is made, and the const means you promise not to change anything. You get the speed of a reference with the safety of a read-only view. If you try to change the element by accident, the compiler stops you at once.

Code story: Three friends' names are stored in a vector of strings. Copying a string costs time and memory, so the program reads each name through a const reference instead. Watch: the names print exactly as stored, and not a single copy was made.

#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    vector<string> names = {"Asha", "Ravi", "Maya"};
    for (const auto& name : names) {
        cout << name << " ";
    }
    cout << endl;
    return 0;
}
Asha Ravi Maya

What just happened? Each name is a read-only view of the real string in the vector β€” no copying, no changing. This is the form professional C++ code uses most often, because it is fast and safe at the same time.

5. Looping over a string

A string is also a container β€” a container of characters. A range-based loop over a string visits every character, one by one. That makes small jobs like counting letters, checking spelling, or cleaning up text very easy.

Code story: A teacher wants to count the vowels in the word "education". The loop looks at each letter in turn and asks: is this letter a, e, i, o or u? Each time the answer is yes, the counter goes up by one. Watch the counter reach 5 β€” the five vowels e, u, a, i and o.

#include <iostream>
#include <string>
using namespace std;

int main() {
    string word = "education";
    int vowels = 0;
    for (char letter : word) {
        if (letter == 'a' || letter == 'e' || letter == 'i' || letter == 'o' || letter == 'u') {
            vowels++;
        }
    }
    cout << "Vowels in " << word << ": " << vowels << endl;
    return 0;
}
Vowels in education: 5

What just happened? The loop visited all nine letters of "education". Five of them β€” e, u, a, i and o β€” are vowels, so the counter ended at 5. Notice the loop variable here is a plain char: a single character is tiny, so copying it costs nothing.

6. Looping over a map

Maps store pairs: a key and a value. When you loop over a map, each element is a pair. The key sits in .first and the value sits in .second. With const auto& you can read every pair without copying a single one.

Code story: A school register maps roll numbers to student names. The program loops over the map and prints each roll number with its name. Watch how the loop hands us one pair at a time, and how the pairs arrive already sorted by roll number β€” that is the map doing its job.

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    map<int, string> students = {{101, "Asha"}, {102, "Ravi"}, {103, "Maya"}};
    for (const auto& student : students) {
        cout << student.first << ": " << student.second << endl;
    }
    return 0;
}
101: Asha
102: Ravi
103: Maya

What just happened? The loop ran once per entry in the map. Each student is a pair: .first is the roll number (the key) and .second is the name (the value). Because a map keeps its keys sorted, the output comes out in roll-number order: 101, 102, 103.

In C++17 and later you can unpack the pair right in the loop: for (const auto& [roll, name] : students) gives you two tidy variables, roll and name, instead of .first and .second. This is called a structured binding, and it is the modern way to loop over maps.

7. Which loop variable should you use?

The four forms look alike, but they behave very differently. Here is the whole choice on one table β€” when in doubt, reach for const auto&:

FormCopies the element?Can change it?Use when
autoYes β€” a fresh copyNoSmall values you only read, like int or double.
auto&No β€” the real elementYesYou need to change the elements.
const auto&No β€” a read-only viewNoBig values you only read, like string. This is the usual default.
auto&&No β€” the real elementYesAdvanced code that must accept anything; rarely needed in everyday programs.

8. Common mistakes

Range-based loops are simple, but five mistakes catch almost every beginner. Learn them now and you will never waste an afternoon on them:

MistakeWhat happensFix
Changing a copy with plain autoThe copy changes, the container stays the same β€” your edit silently disappears.Use auto& when you want to change the elements.
Looping over big strings with plain autoEvery string is copied on every step, which is slow and wasteful.Use const auto& to read big values without copying.
Adding or removing elements while loopingThe loop's hidden iterators break, and the program may crash or behave strangely.Collect the changes first, then apply them after the loop finishes.
Trying to use an index, like scores[i]There is no index in a range-based loop, so the code does not compile.Use the loop variable itself, or keep a counter outside the loop.
Holding a reference to a temporary, like for (auto& x : getScores())The temporary dies at the end of the line and the reference dangles.Use plain auto (a copy) when the container is a temporary.

9. Mini program: the shopping cart total

Time to put it all together. Our final program is a small shopping cart: a vector of prices, one range-based loop to add them up, and a tidy summary at the end.

Code story: A customer has four items in the cart, priced 12.50, 40.00, 23.00 and 100.00. The program starts the total at zero, then loops over the prices and adds each one to the total. At the end it prints how many items there are and what the customer must pay. Watch the total grow: 12.5, then 52.5, then 75.5, then 175.5.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<double> prices = {12.5, 40.0, 23.0, 100.0};
    double total = 0;
    for (auto price : prices) {
        total += price;
    }
    cout << "Items: " << prices.size() << endl;
    cout << "Total: " << total << endl;
    return 0;
}
Items: 4
Total: 175.5

What just happened? The loop added each price to total in turn: 12.5, then 52.5, then 75.5, then 175.5. The whole program is seven lines of real logic β€” no index, no size check, no room for an off-by-one error. That is the range-based loop doing what it does best: letting you think about the job, not the counting.

Practice β€” try it yourself

1. Create a vector<string> of five fruits and print each one with a range-based for loop.

2. Use auto& to add 5 marks to every score in a vector<int> of your marks, then print the new scores.

3. Use const auto& to print a list of your favourite city names β€” no copies allowed.

4. Write a program that finds the longest word in a vector<string> using a range-based for loop.

What does the loop for (auto x : scores) do?


Which loop variable lets you change the real elements?


What happens if you change x inside the loop for (auto x : scores)?


Fill in the blanks β€” type the missing word into the box.

A range-based for loop visits every of a container.


Use to change the real elements while you loop.


The range-based for loop was added in C++ .


Up next

Lesson 52 β€” Move Semantics

See how C++ moves resources instead of copying them, and why moving is so much faster than copying.