Lesson overview
Loops are not merely syntax for repetition. They encode assumptions about how many times work should happen, how state changes, how data is traversed, and when execution should stop. This lesson uses C++ loops to connect correctness, clarity, locality and measurement.
Learning objectives
- Choose between counted
for, range-basedforand sentinel-controlledwhileorforloops. - Keep loop control variables monotonic and separate from user input or unrelated state.
- Use index bounds that match container size and avoid off-by-one errors.
- Compare index-based loops with range-based loops and standard algorithms.
- Repair a flawed input loop by separating control, input and termination.
Key vocabulary before you start
Iteration
Repeated execution controlled by a condition, counter or range.
Loop invariant
A condition that should remain true before and after each iteration.
Termination condition
The condition that stops the loop.
Control variable
State used to determine progress through a loop.
Sentinel value
A special input value used to signal stop.
Off-by-one error
A boundary mistake that runs one iteration too many or too few.
Loop discipline map
A loop is a contract about repetition. Good loops make the controlling state, data traversal and stopping condition visible.
Match loop form to purpose
Use counted loops for known counts, range loops for elements and sentinel loops for input-driven repetition.
Protect the control variable
Do not reuse the loop counter as a user input variable or unrelated state holder.
Prefer clear bounds
Use < rather than <= for zero-based indices and use size-aware types for container bounds.
Write clear loops first
Prefer readable traversal, then measure before optimising for locality, branching or algorithms.
Example: counted loop vs range-based loop
Both loops sum even values. The counted loop exposes indices; the range-based loop exposes intent.
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6};
int sum = 0;
for (size_t i = 0, n = v.size(); i < n; ++i) {
if (v[i] % 2 == 0) sum += v[i];
}
std::cout << "sum=" << sum << '\n';
int sum2 = 0;
for (int x : v) {
if ((x & 1) == 0) sum2 += x;
}
std::cout << "sum2=" << sum2 << '\n';
}Trace the state
- The counted loop creates
ias the control variable andnas a cached size boundary. - The condition
i < nmatches zero-based indexing and avoids the common<=off-by-one error. - The range-based loop hides indexing because the logic only needs element values.
- Both loops separate accumulation state from traversal state.
| Line or operation | Variable | Value | State role | Invariant or check |
|---|---|---|---|---|
| Read the example before running it | Key names and calls | Known from source | Reasoning setup | Each named element should support the lesson focus: Loops, Control Variables and Iteration. |
| Trace the first meaningful operation | Primary state or boundary | Established by initialisation, call or condition | Valid-state checkpoint | Later reasoning must not use the value before this checkpoint. |
| Identify the first decision or dereference | Control or access point | Depends on current state | Failure-mode checkpoint | The condition, pointer, argument or dependency must be valid before use. |
| Record the observable result | Return value, output or mutation | Produced by the example | Evidence checkpoint | The result should match the contract explained in the lesson. |
Visual model: loop reasoning cycle
A loop is safe only when setup, progress and termination can all be stated clearly.
Initialise stateSet counters, accumulators and inputs to valid starting values.
Check continuationDecide whether the next iteration is allowed.
Do useful workRead or transform data without corrupting loop control.
Make progressMove toward termination through a counter, iterator, input read or timeout.
Exit with invariantAfter the loop, state what is known about the result.
Invariant task
For the sentinel-input exercise, state the invariant before the body and the condition that permits another iteration.
Choose the loop form
Use a counted for loop when the iteration count or index position matters. The control variable should move predictably toward termination.
Use a range-based for loop when the task is element-focused and does not need indices. This often communicates intent better than manual indexing.
Use a while loop, or a for loop with the condition in the middle slot, when repetition is controlled by input, a sentinel value or an external condition.
Applied task
Classify three loops as counted, range-based or sentinel-controlled, then explain why each form fits.
Control variable discipline
The loop control variable should be boring: initialised clearly, changed predictably and tested against a visible termination condition.
Do not use the control variable as the place where user input is stored. That makes the loop's progress depend on external values and creates fragile behaviour.
If user input controls termination, name it as input and express the sentinel condition directly.
Applied task
Take a loop that reads input and identify which variable controls repetition, which stores input and which stores accumulated output.
Bounds, locality and measurement
For zero-based indexing, prefer i < size to i <= size - 1. It is easier to read, handles empty ranges more naturally and matches iterator-style thinking.
Use size_t or an appropriate container size type when comparing against container sizes. Avoid signed/unsigned mismatches unless you have a deliberate reason.
Maximise locality where it supports clarity: contiguous data, simple branches and predictable strides are friendly to modern hardware. Write clear loops first, then measure before optimising.
Applied task
Explain why i < v.size() is usually safer than i <= v.size() in a vector traversal.
From loops to algorithms
A clear loop is often the right first expression of an idea. Once the intent is stable, consider whether a standard algorithm or ranges pipeline communicates it better.
std::accumulate, std::count_if, std::copy_if and C++ ranges can reduce incidental loop mechanics, but they can also hide simple logic behind unfamiliar abstractions.
The trade-off is not loops versus cleverness. It is whether the code makes traversal, filtering, accumulation and cost easier to inspect.
Example: counted loop and range-based loop
The range-based form makes the element-level intent clearer when indices are not needed.
// Counted for
int sum = 0;
for (size_t i = 0, n = v.size(); i < n; ++i) {
if (v[i] % 2 == 0) sum += v[i];
}
std::cout << "sum=" << sum << '\n';
// Range-based for (clearer intent)
int sum2 = 0;
for (int x : v) {
if ((x & 1) == 0) sum2 += x;
}
std::cout << "sum2=" << sum2 << '\n';Applied task
Rewrite the even-number sum as std::accumulate or a ranges pipeline, then explain whether readability improved.
Summary checklist
Use for when iteration count is known, range-based for when you just need elements and while when a sentinel controls input.
Keep the control variable monotonic and separate from user inputs or side effects.
Prefer < to <= for index bounds, and use size_t or another appropriate size type for sizes and indices.
Maximise locality with contiguous data, simple branches and predictable strides.
Let the compiler optimise. Write clear loops first, then measure.
Applied task
Choose one checklist item and find a before/after example where applying it makes a loop safer or clearer.
Applied case lab
Case 1: Index required
You need to print both positions and values from a vector. Decide whether counted indexing or a range loop with a separate index communicates the intent better.
Case 2: Sentinel input
A loop should keep reading numbers until 0. Decide where the input variable should live and what should appear in the condition.
Case 3: Algorithm refactor
A loop filters and sums values. Decide whether a standard algorithm makes the intent clearer or hides useful state transitions.
Applied task: Fix the Loop
Repair flawed loop control by separating loop state, user input and termination conditions.
Stage 1: read and classify
A. Diagnose the broken loop
The user should enter numbers. Print 100 + input until the user enters 0. The current code is wrong.
int x = 100;
for (int y = 0; y < x; ++y) {
std::cin >> y; // wrong: y is both control and input
std::cout << x + y << '\n';
}- Identify the loop control variable.
- Explain why reading into
ycorrupts the loop control logic. - Separate the constant offset, input value and sentinel condition.
Reveal one possible refactor
int x = 100;
for (int input = 0; std::cin >> input && input != 0; ) {
std::cout << x + input << '\n';
}Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
Stage 2: trace or repair
B. Write a while alternative
Express the same sentinel-driven logic with while if that makes input control clearer.
int x = 100;
int input = 0;
// TODO: read until input is 0- Write a
whileloop that reads before printing. - Handle failed input by stopping cleanly.
- Compare readability with the compact
forversion.
Reveal one possible refactor
int x = 100;
int input = 0;
while (std::cin >> input && input != 0) {
std::cout << x + input << '\n';
}Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
Stage 3: justify the design
C. From loops to algorithms
Start from the even-number sum and consider whether a standard algorithm helps.
int sum = 0;
for (int x : v) {
if ((x & 1) == 0) sum += x;
}- Rewrite using
std::accumulate. - Explain the readability trade-off.
- State which version you would keep in teaching code and why.
Reveal one possible refactor
int sum = std::accumulate(v.begin(), v.end(), 0, [](int total, int x) {
return (x % 2 == 0) ? total + x : total;
});Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
Quick checks
1. When is a range-based for loop usually the clearest choice?
2. Why is reading user input into the loop counter risky?
3. Which bound is usually safer for indexing a zero-based vector?
AI-augmented practice notes
Use these as prompts for disciplined support, not as permission to outsource judgement.
From loops to algorithms
- Ask AI to rewrite a loop as
std::accumulateor a C++ ranges pipeline. - Ask it to comment on readability, debugger experience and performance trade-offs.
- Keep the original clear loop when the algorithm version hides too much for the learner.
Repair recipe
- Ask AI to identify the control variable and propose a loop where control and input are separate.
- Ask AI to generate tests for empty input, a single value, a long sequence and invalid input handling.
- Use the generated cases to check whether the sentinel condition is correct.
Loop trace table
- Ask AI to build an iteration table showing control variable, input value, condition result and accumulated state.
- Use the table to find off-by-one and non-termination bugs.
- Check the table manually for the first, final and empty-input cases.
Assessment tasks
- Repair the broken input loop and explain why the original mixed control and input state.
- Write counted, range-based and sentinel-controlled versions of small loops and justify each choice.
- Rewrite one loop as a standard algorithm, then decide which version you would keep.
- Create a loop trace table for an input sequence ending in
0.
Judgement questions
When does a loop counter improve clarity, and when does it distract?
Use the difference between index-based and range-based traversal.
Why should user input not usually be the loop counter?
Discuss control, predictability and termination.
When should you replace a loop with an algorithm?
Balance readability, debugging and performance measurement.
