Phase 1 · Modern C++ FoundationsModule 5~42 min read

Loops & Iteration

Repeat work with while, do-while, classic for, and range-based for while reasoning about boundaries and termination.

What you'll learn

A loop repeats work while preserving a contract: state starts valid, each iteration makes progress, and the condition eventually becomes false. C++ also lets you iterate directly over elements, avoiding indexes when the index itself is not meaningful.

By the end, you'll be able to:

  • Select between while, do-while, classic for, and range-based for
  • Iterate over elements by value or reference intentionally
  • Apply counting, accumulation, search, and validation patterns
  • Prevent off-by-one errors with half-open ranges and invariants
The state cycle behind a terminating loop
Initialize state
→
Check condition
→
Execute body
→
Advance state
↩

while loops

A while loop checks its condition before every iteration. Use it when repetition depends on a changing condition and the number of iterations is not naturally a fixed count.

countdown.cpp
#include <iostream>

int main() {
    int fuel{3};

    while (fuel > 0) {
        std::cout << "Fuel: " << fuel << '\n';
        --fuel;
    }

    std::cout << "Engine stopped\n";
}

Key idea

Identify the progress step before writing the body. Here, --fuel moves the state toward fuel == 0. Without it, the loop never terminates.

do-while loops

A do-while executes the body before checking its condition, so it always runs at least once. Menus and retry prompts are common uses.

menu.cpp
#include <iostream>

int main() {
    char command{};

    do {
        std::cout << "[p]lay [h]elp [q]uit: ";
        std::cin >> command;

        if (command == 'p') std::cout << "Starting game\n";
        else if (command == 'h') std::cout << "Choose p or q\n";
    } while (std::cin && command != 'q');

    std::cout << "Goodbye\n";
}

Watch out

The do-while syntax ends with a semicolon after the condition. Also include stream state in interactive conditions so end-of-input cannot trap the program in a loop.

Classic for loops

A classic for keeps initialization, condition, and advancement together. It is a strong fit when a counter or iterator defines progress.

sum.cpp
#include <iostream>

int main() {
    int total{};

    for (int number{1}; number <= 5; ++number) {
        total += number;
        std::cout << number << (number < 5 ? " + " : " = ");
    }

    std::cout << total << '\n';
}
PartRunsResponsibility
initializeronceCreate loop state
conditionbefore every iterationDecide whether the body may execute
bodywhen condition is truePerform the repeated work
advancementafter each completed bodyMove toward termination

Range-based for

A range-based for visits every element in a range. Iterate by value when a cheap copy is useful, by reference when modifying elements, and by const reference when reading larger objects without copying.

range_for.cpp
#include <cctype>
#include <iostream>
#include <string>

int main() {
    std::string language{"Modern C++"};

    for (char& character : language) { // reference modifies each element
        unsigned char safe{static_cast<unsigned char>(character)};
        character = static_cast<char>(std::toupper(safe));
    }

    for (const char character : language) { // read each value
        std::cout << character;
    }
    std::cout << '\n';
}
DeclarationEffect
auto elementCopy each element
auto& elementRefer to and potentially modify each element
const auto& elementRead each element without copying

Note

Character-classification functions require either EOF or a value representable asunsigned char. The conversion prevents undefined behavior when plainchar is signed and contains a negative value.

break & continue

continue skips the rest of the current iteration;break exits the nearest loop. Both are useful when they make the main path clearer, but several scattered exits can obscure the loop's contract.

loop_control.cpp
#include <iostream>

int main() {
    for (int value{1}; value <= 10; ++value) {
        if (value % 2 != 0) continue; // skip odd values

        std::cout << value << ' ';
        if (value == 6) break;        // stop the nearest loop
    }
    std::cout << '\n';
}

Tip

Prefer a direct loop condition when it naturally expresses termination. Usewhile (true) plus a focused break when the exit decision occurs only after reading or computing state inside the body.

Core loop patterns

PatternState maintained
CountHow many elements satisfy a condition
AccumulateA running sum, product, or combined result
SearchWhether or where a target was found
TransformA changed output for each input element
ValidateRepeat until input satisfies syntax and domain rules
patterns.cpp
#include <iostream>
#include <string>

int main() {
    std::string text{"resource safety"};
    int vowels{};
    bool found_space{false};

    for (const char character : text) {
        if (character == ' ') found_space = true;
        if (character == 'a' || character == 'e' ||
            character == 'i' || character == 'o' || character == 'u') {
            ++vowels;
        }
    }

    std::cout << "vowels=" << vowels
              << " space=" << std::boolalpha << found_space << '\n';
}

Key idea

Name state after its meaning—vowels and found_space—so the loop reads as a maintained contract rather than a collection of counters.

Nested loops

A nested loop completes all inner iterations for each outer iteration. This naturally models rows and columns, combinations, grids, and repeated grouped output.

table.cpp
#include <iomanip>
#include <iostream>

int main() {
    for (int row{1}; row <= 4; ++row) {
        for (int column{1}; column <= 4; ++column) {
            std::cout << std::setw(4) << row * column;
        }
        std::cout << '\n';
    }
}

Note

If both loops scale with input size, the work may grow quadratically. Correctness comes first; later modules teach complexity analysis and standard algorithms.

Boundaries & invariants

C++ ranges conventionally use half-open boundaries: include the beginning, exclude the end. For a sequence of size n, valid indexes are[0, n).

indexes.cpp
#include <iostream>
#include <string>

int main() {
    std::string word{"C++"};

    // Valid indexes form the half-open range [0, word.size()).
    for (std::size_t index{0}; index < word.size(); ++index) {
        std::cout << index << ':' << word[index] << '\n';
    }
}
  • Write index < size, not index <= size
  • Keep the index type compatible with the size type
  • State what is already true before each iteration—the loop invariant
  • Prove the body preserves the invariant
  • Identify why progress must reach termination

Watch out

Counting backward with an unsigned size can wrap below zero and continue with a huge value. Prefer reverse iterators, range views, or a carefully chosen signed index when reverse traversal is required.

Recap & quick check

Key takeaways

  • while checks before iteration; do-while guarantees one body execution.
  • Classic for centralizes counter state; range-for expresses element traversal directly.
  • References let a range loop modify original elements; const references avoid copies while reading.
  • break exits the nearest loop and continue skips to its next iteration.
  • Half-open ranges, progress, and loop invariants prevent common boundary defects.

Quick check

1. Which loop always executes its body at least once?

2. Which range-for declaration can modify original elements?

3. What are valid indexes for a sequence of size n?

4. What must every terminating loop do?

You can now repeat work with explicit boundaries. Next: Module 6 — Functions, Scope, References & Program Structure, where repeated logic becomes reusable design.