Lesson 58 min read

Loops

The old classics meet modern convenience β€” range-based for is the star of the show

The Loop Family

Loops are the workhorses of programming β€” they repeat a block of code until a condition is met. C++ inherits all three classic loop forms from C (for, while, do-while) and adds a modern superstar: the range-based for loop.

If the classic for loop is a manual transmission β€” powerful but requires you to manage every gear shift β€” the range-based for is automatic transmission. Same destination, less fiddling.

The Classic for Loop

#include <iostream>
using namespace std;
int main() {
// Classic for β€” you control init, condition, and increment
cout << "Counting up: ";
for (int i = 1; i <= 5; i++) {
cout << i << " ";
}
cout << endl;
// Counting down
cout << "Countdown: ";
for (int i = 5; i > 0; i--) {
cout << i << " ";
}
cout << "Liftoff!" << endl;
// Stepping by 2
cout << "Evens: ";
for (int i = 0; i <= 10; i += 2) {
cout << i << " ";
}
cout << endl;
return 0;
}
Output
Counting up: 1 2 3 4 5
Countdown: 5 4 3 2 1 Liftoff!
Evens: 0 2 4 6 8 10

while and do-while

while checks the condition before each iteration β€” if the condition is false from the start, the body never runs. do-while checks after β€” the body always runs at least once.

do-while is perfect for menus: you always want to show the menu at least once before asking if the user wants to continue.

while and do-while

#include <iostream>
using namespace std;
int main() {
// while β€” might not execute at all
int fuel = 3;
cout << "Driving..." << endl;
while (fuel > 0) {
cout << " Fuel left: " << fuel << endl;
fuel--;
}
cout << "Out of gas!" << endl;
cout << endl;
// do-while β€” always executes at least once (great for menus)
int choice;
do {
cout << "--- Menu ---" << endl;
cout << "1. Play" << endl;
cout << "2. Settings" << endl;
cout << "3. Quit" << endl;
choice = 3; // simulate user picking Quit
cout << "You chose: " << choice << endl;
} while (choice != 3);
cout << "Goodbye!" << endl;
return 0;
}
Output
Driving...
  Fuel left: 3
  Fuel left: 2
  Fuel left: 1
Out of gas!

--- Menu ---
1. Play
2. Settings
3. Quit
You chose: 3
Goodbye!

Range-Based for β€” The Star of the Show

Introduced in C++11, the range-based for loop is the modern way to iterate over collections. No index management, no off-by-one errors, no iterator boilerplate. Just tell C++ what to loop over, and it handles the rest.

The syntax is: for (auto& element : collection)

Think of it as saying: "for each element in this collection, do something."

Range-Based for with Vectors

#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
vector<int> scores = {95, 87, 92, 78, 100};
// Range-based for β€” clean and readable
cout << "Scores: ";
for (const auto& score : scores) {
cout << score << " ";
}
cout << endl;
// Modifying elements β€” use auto& (without const)
for (auto& score : scores) {
score += 5; // curve everyone up by 5
}
cout << "Curved: ";
for (const auto& score : scores) {
cout << score << " ";
}
cout << endl;
// Works with strings too
vector<string> names = {"Alice", "Bob", "Carol"};
for (const auto& name : names) {
cout << "Hello, " << name << "!" << endl;
}
return 0;
}
Output
Scores: 95 87 92 78 100
Curved: 100 92 97 83 105
Hello, Alice!
Hello, Bob!
Hello, Carol!
Note: Always use & in range-based for: for (auto& x : vec). Without &, you get a copy of each element β€” modifications won't affect the original, and you pay a performance cost copying every element. Use const auto& when you just need to read, and auto& when you need to modify.

C++17: Structured Bindings in Loops

When iterating over a map, each element is a pair with .first (key) and .second (value). Before C++17, this was clunky. With structured bindings, you can unpack the pair right in the loop header β€” naming the key and value whatever you want.

Structured Bindings with Maps (C++17)

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> ages = {
{"Alice", 30},
{"Bob", 25},
{"Carol", 28}
};
// Before C++17 β€” clunky .first and .second
cout << "Old style:" << endl;
for (const auto& pair : ages) {
cout << " " << pair.first << " is " << pair.second << endl;
}
// C++17 structured bindings β€” clean and readable!
cout << "\nC++17 style:" << endl;
for (const auto& [name, age] : ages) {
cout << " " << name << " is " << age << endl;
}
// Modifying values with structured bindings
for (auto& [name, age] : ages) {
age++; // happy birthday everyone!
}
cout << "\nAfter birthdays:" << endl;
for (const auto& [name, age] : ages) {
cout << " " << name << " is now " << age << endl;
}
return 0;
}
Output
Old style:
  Alice is 30
  Bob is 25
  Carol is 28

C++17 style:
  Alice is 30
  Bob is 25
  Carol is 28

After birthdays:
  Alice is now 31
  Bob is now 26
  Carol is now 29

break and continue β€” Loop Control

break exits the loop entirely β€” like pulling the emergency stop on a conveyor belt. continue skips the rest of the current iteration and jumps to the next one β€” like tossing a defective item off the belt and keeping the line moving.

break and continue

#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> numbers = {1, 2, -1, 4, 5, -1, 7, 8};
// continue β€” skip negative numbers
cout << "Positives only: ";
for (const auto& n : numbers) {
if (n < 0) continue; // skip this iteration
cout << n << " ";
}
cout << endl;
// break β€” stop at first negative
cout << "Until negative: ";
for (const auto& n : numbers) {
if (n < 0) break; // exit the entire loop
cout << n << " ";
}
cout << endl;
// Searching with break
vector<string> names = {"Alice", "Bob", "Carol", "Dave"};
string target = "Carol";
for (const auto& name : names) {
if (name == target) {
cout << "Found " << target << "!" << endl;
break;
}
}
return 0;
}
Output
Positives only: 1 2 4 5 7 8
Until negative: 1 2
Found Carol!
Challenge

Quick check

What is the difference between for (auto x : vec) and for (auto& x : vec)?
← ConditionalsArrays & Vectors β†’