Lesson overview
for loops are ideal when the iteration count is known or upper-bounded at loop entry. Many real tasks do not have a known count in advance: reading an unknown-length file, accepting user input until a sentinel or polling a device. while and do-while loops evaluate a condition each iteration and rely on state that changes during the loop to terminate. This power brings risk: they can execute zero times, or forever, if the condition never becomes false.
Learning objectives
- Distinguish
whilefromdo-whilein terms of first-condition timing and minimum execution count. - Choose
for,whileordo-whilebased on iteration count, sentinel input and task shape. - State the loop invariant and termination condition for a condition-driven loop.
- Refactor fragile
while(true)loops into condition-driven loops with clear exits. - Identify non-termination risks caused by state that fails to change.
Key vocabulary before you start
Pre-condition loop
A loop such as while that checks before the body runs.
Post-condition loop
A loop such as do-while that checks after one body execution.
Sentinel-controlled loop
A loop that stops when a special input value appears.
Stale read
Using a value from an earlier iteration after a failed read.
Runaway loop
A loop with no reliable path to termination.
Progress measure
The value or condition that moves toward loop exit.
Condition-driven loop map
A condition-driven loop is only safe when its state changes move it toward a visible exit.
Choose by entry knowledge
for fits known counts; while and do-while fit conditions discovered during execution.
Know zero vs one
while may execute zero times; do-while executes at least once.
Make progress visible
Some state must change each iteration so the condition can eventually become false.
Prefer explicit exits
Avoid while(true) unless the exit is local, obvious and justified.
Example: while vs do-while
Both loops depend on a changing control variable, but they check the condition at different points.
// while: may not run at all
int x = 0;
while (x < 5) {
++x;
}
// do-while: runs at least once
int y = 10;
do {
--y;
} while (y > 10);Trace the state
whilechecksx < 5before entering the body.- If the initial condition is false, the
whilebody executes zero times. do-whileexecutes the body first, then checks the condition.- Both loops rely on
++x,--yor another explicit state update to move state toward termination.
| 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: While and Do-While Loops: Non-Deterministic 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: condition-driven iteration
Condition-driven loops should make first execution, progress and exit evidence visible.
Decide first-execution ruleChoose while when zero runs are valid; choose do-while when one run is required.
Read or check statePlace the condition where it protects the body from stale values.
Execute bodyDo work only when the current state is valid.
Update progressChange input, counter, timeout or state so termination remains possible.
Handle exit reasonDistinguish sentinel, failed input, timeout and success.
Exit-reason task
For each loop in the mini exercise, name the exact exit reason and the value that proves progress.
Pick the right loop
for loops are ideal when the iteration count is known or upper-bounded at loop entry. This is even safer when the count is a compile-time constant.
while and do-while loops are for tasks where the count is discovered as execution proceeds: unknown-length input, sentinel values, retries, polling and state-machine progress.
The key question is not which syntax looks shortest. The key question is where the stopping condition lives and which state changes make termination inevitable.
Applied task
Given a task description, choose for, while or do-while, then state the invariant and termination condition.
While vs do-while: the basics
while checks the condition at the start of each iteration, so it may execute zero times.
do-while checks the condition at the end, so it executes at least once. That makes it suitable when one attempt must happen before the result can be tested.
Both rely on one or more control variables changing each iteration so the condition eventually becomes false.
Example: first-condition vs final-condition
The difference is visible when the initial state already fails the condition.
// while: may not run at all
int x = 0;
while (x < 5) {
++x;
}
// do-while: runs at least once
int y = 10;
do {
--y;
} while (y > 10);Applied task
Choose an initial value that makes a while loop run zero times, then explain what a do-while version would do.
Classic use case: sentinel-controlled input
A sentinel is a special value that means stop. For example, read integers until the user enters 0, and print 100 + input for every other value.
A robust input loop should combine the read and the test. That means it progresses when input succeeds, exits on the sentinel and also exits cleanly if extraction fails.
This avoids stale values: the body only runs after a fresh value has actually been read.
Read until zero
The condition both consumes input and tests the sentinel.
#include <iostream>
int main() {
int value{};
while (std::cin >> value && value != 0) {
std::cout << 100 + value << '\n';
}
}Applied task
Explain why putting std::cin >> value in the condition prevents the loop from processing a stale value.
File reading: avoid the while(!in.eof()) trap
eof() is only set after a read has failed. If you test !in.eof() before attempting the read, the loop may run one extra time and process stale data.
The safer pattern is the same as for console input: make the read operation the condition. The body runs only when the read succeeded.
This is a professional habit. Loop conditions should describe the actual permission to use the value inside the body.
Wrong: checking eof before reading
The final iteration may reuse the previous line.
#include <fstream>
#include <string>
std::ifstream in("data.txt");
std::string line;
while (!in.eof()) {
std::getline(in, line);
// last iteration may process stale 'line'
}Right: read as the condition
The body only executes when getline succeeds.
std::ifstream in("data.txt");
std::string line;
while (std::getline(in, line)) {
// use 'line'
}Applied task
Rewrite one while(!stream.eof()) loop so the read operation controls entry to the loop body.
Escape hatches: guard against runaways
Non-deterministic loops need safety valves. If the loop depends on external state, input, files, devices or work queues, the program should have a way to escape when progress stops.
Common guard rails include break on failure, a max-iteration watchdog and a deadline timeout based on std::chrono::steady_clock.
These guard rails do not replace correct loop logic. They document failure modes and stop a bad state from becoming a runaway process.
Bound a loop by attempts and time
The loop exits on failure, after too many attempts or after a deadline.
#include <chrono>
bool work_once();
bool bounded_loop() {
using clock = std::chrono::steady_clock;
auto deadline = clock::now() + std::chrono::seconds(2);
int attempts = 0;
const int max_attempts = 10000;
while (clock::now() < deadline && attempts < max_attempts) {
if (!work_once()) break; // escape on failure
++attempts;
}
return attempts > 0;
}Applied task
For a polling loop, state what each escape hatch means: success, failure, timeout and max-attempt exhaustion.
Common logical errors: why novices struggle
The classic failure is a condition that never changes: the loop depends on a variable, but the body never updates it.
Input loops also fail when they test stale values, read without checking success or hit stream failure and never recover.
continue can hide the same bug. If it skips the update step, the loop may keep testing the same state forever.
Infinite loop: condition never changes
The condition depends on n, but the body never moves n toward zero.
int n = 5;
while (n > 0) {
std::cout << n << '\n';
// forgot: --n;
}Applied task
List every variable in one loop condition and point to the exact statement that changes each one.
Worked example: read lines, sum numbers
Nested condition-driven loops are safe when each loop has its own progress point.
The outer loop progresses by reading a line from the file. The inner loop progresses by extracting one number from that line.
Malformed tokens naturally stop the inner extraction loop for that line; a production version may additionally report or reject malformed input depending on the file contract.
Read unknown lines and unknown tokens
Each loop condition performs the read operation that makes the body safe.
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
int main() {
std::ifstream in("nums.txt");
if (!in) {
std::cerr << "Cannot open file\n";
return 1;
}
long long sum = 0;
std::string line;
while (std::getline(in, line)) {
std::istringstream iss(line);
long long x;
while (iss >> x) {
sum += x;
}
}
std::cout << "Total = " << sum << '\n';
}Applied task
Identify the progress point for the outer loop and the progress point for the inner loop.
Summary checklist
Use while or do-while when the iteration count is not known at entry.
Make the read or advance action part of the condition so the loop progresses or ends.
Prefer do-while for menus and cases that must run once.
Add escape code: break on error, watchdog counters or timeouts.
Beware stale values, unconsumed input, eof() misuse, forgotten updates and continue paths that skip progress.
State the loop invariant and the termination measure. If you cannot, the loop is risky.
Applied task
Use the checklist to review one loop from your own codebase.
State change and termination
Condition-driven loops are powerful because the condition can depend on input, a device, a file, a queue or a protocol state. They are dangerous for the same reason.
A loop that reads input must account for successful input, sentinel input and failed input. A loop that polls state must account for progress, timeout and error paths.
A professional review of a while loop should ask: what changes, what stays invariant, and what makes the condition false?
Applied task
For one while loop, write the invariant, the changing state and the exact reason it must eventually stop.
Repairing fragile while(true) loops
while(true) can be acceptable in tight systems code when the exit is immediate and obvious, but it often hides the real condition.
If the loop has one clear reason to continue, put that reason in the condition. If it has several exit paths, consider naming them or extracting a small state machine.
The repair is not just cosmetic. A condition-driven loop makes termination easier to test and explain.
Applied task
Rewrite a while(true) loop so the condition states why the loop continues.
Applied case lab
Case 1: Unknown input length
A program reads numbers until the user enters 0. Decide whether this should be a counted for, a while or a do-while, and state the sentinel.
Case 2: Mandatory first attempt
A menu should display at least once before checking whether the user wants to quit. Decide whether do-while communicates that better than while.
Case 3: Polling a device
A loop polls until a device is ready. Identify the progress state, timeout strategy and error exit.
Applied task: make the exit visible
Practise choosing condition-driven loops and proving they terminate.
Stage 1: read and classify
A. Choose the loop
Classify each task as for, while or do-while.
// 1. Print exactly 10 rows.
// 2. Read values until 0.
// 3. Show a menu at least once, then repeat until Quit.
// 4. Poll until ready or timeout.- Choose a loop form for each task.
- State what controls termination.
- Identify which tasks may execute zero times and which must execute once.
Reveal one possible refactor
1 -> counted for
2 -> while or condition-driven for
3 -> do-while
4 -> while with timeout/error stateModel 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. Repair while(true)
Move the real continuation rule into the condition.
while (true) {
std::cin >> input;
if (input == 0) break;
process(input);
}- Rewrite the loop without
while(true). - Handle failed input as well as sentinel input.
- Explain which state changes each iteration.
Reveal one possible refactor
while (std::cin >> input && input != 0) {
process(input);
}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. Prove termination
This loop looks simple, but the proof depends on progress.
int x = 10;
while (x > 0) {
// TODO: make progress
}- Add a state update that guarantees termination.
- State the decreasing measure.
- Explain how the loop could become infinite.
Reveal one possible refactor
int x = 10;
while (x > 0) {
--x; // decreasing measure: x moves toward 0
}Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
Stage 4: test the boundary
D. Repair stale file input
The loop checks EOF before attempting the read.
std::ifstream in("data.txt");
std::string line;
while (!in.eof()) {
std::getline(in, line);
process(line);
}- Explain why the loop can process a stale
line. - Move the read into the condition.
- State the exact condition that permits
process(line)to run.
Reveal one possible refactor
std::ifstream in("data.txt");
std::string line;
while (std::getline(in, line)) {
process(line);
}Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
Stage 5: evaluate alternatives
E. Choose do-while for a menu
The user must see the menu before choosing whether to quit.
int choice{};
// TODO: show menu at least once, then repeat until choice == 0- Write a
do-whileloop for the menu. - Name the sentinel value.
- Add a failed-input guard.
Reveal one possible refactor
char choice{};
do {
std::cout << "[A]dd [R]emove [Q]uit: ";
if (!(std::cin >> choice)) break;
switch (choice) {
case 'A': case 'a': add(); break;
case 'R': case 'r': remove(); break;
case 'Q': case 'q': std::cout << "Bye!\n"; break;
default: std::cout << "Unknown option\n"; break;
}
} while (choice != 'Q' && choice != 'q');Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
F. Add escape hatches
This polling loop has no upper bound.
while (!ready()) {
work_once();
}- Add a max-iteration counter.
- Add a
steady_clockdeadline. - Document what each exit path means.
Reveal one possible refactor
using clock = std::chrono::steady_clock;
auto deadline = clock::now() + std::chrono::seconds(2);
int attempts = 0;
const int max_attempts = 10000;
while (!ready() && attempts < max_attempts && clock::now() < deadline) {
if (!work_once()) break;
++attempts;
}Model reasoning
A strong answer explains why the change preserves behaviour, what failure mode it removes, and which trade-off remains acceptable.
G. From while(true) to correct logic
This loop should read tokens until EOF or quit, but malformed input can leave stale state.
#include <iostream>
#include <string>
int main() {
std::string s;
while (true) {
std::cin >> s; // may fail and leave 's' unchanged
if (s == "quit") break;
std::cout << s << '\n';
}
}- Make the read operation the condition.
- Keep
quitas the sentinel. - Explain what happens on EOF or input failure.
Reveal one possible refactor
int main() {
std::string s;
while (std::cin >> s) {
if (s == "quit") break;
std::cout << s << '\n';
}
}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. Which loop may execute zero times?
2. What is the main risk of condition-driven loops?
3. When is do-while a good fit?
4. Why is while (!in.eof()) usually the wrong file-reading pattern?
5. What should a sentinel-controlled input loop normally put in its condition?
6. What is the most direct proof that a loop is not stuck?
7. Which guard rail helps a polling loop avoid running forever?
AI-augmented practice notes
Use these as prompts for disciplined support, not as permission to outsource judgement.
Build a menu skeleton
- Ask AI to scaffold a
do-whilemenu with input validation and a help option. - Ask it to factor actions into functions so the loop stays readable.
- Check that the quit sentinel appears in both the
switchand the loop condition.
Add safety valves
- Ask AI to add a watchdog counter or
steady_clockdeadline to an existing loop. - Ask it to document each failure mode and what each
breakmeans. - Use the result to separate normal termination from timeout, failure and cancelled work.
Find the stuck state
- Paste the loop and ask which variables must change each iteration.
- Ask where to instrument those variables with logging or debugger watch expressions.
- Check
continuepaths carefully because they often skip the update that would have made progress.
Verify progress
- Ask AI to identify all progress points in nested loops.
- Ask it to generate small test files: empty, one line, malformed and many lines.
- Trace successful reads, failed reads and sentinel exits separately.
From while(true) to correct logic
- Ask AI to replace
while(true)with a condition that both consumes input and tests it. - Ask it to suggest a sentinel, an error path and a bounded retry policy.
- Review the rewritten loop to ensure the body cannot process stale values.
Prove it ends
- Ask AI to identify the variant: the measure that moves toward termination.
- Ask it to propose guard rails such as maximum-iteration counters, timeouts or explicit error exits.
- Check the proposed guard rails yourself so they do not hide an invalid loop design.
Pick the right loop
- Describe your task and ask AI to choose
for,whileordo-while. - Ask AI to state the loop invariant and termination condition.
- Ask it to rewrite fragile
while(true)loops into condition-driven loops with clear exits.
Robust input patterns
- Ask AI to generate input conditions that both read and test values.
- Ask it to add invalid-input handling using
std::cin.clear()and token skipping where recovery is appropriate. - Ask for tests covering successful input, sentinel input, failed input and empty input.
Generate correct I/O loops
- Provide the file format and ask for a read loop where the read operation controls the body.
- Ask for counters and limits when untrusted files could be extremely large.
- Ask AI to explain exactly when the loop exits and what data is safe to use inside the body.
Termination audit
- Ask AI to identify the state that changes each iteration.
- Ask it to propose empty-input, failed-input, sentinel and timeout tests.
- Verify the result by tracing the first, last and failure iterations yourself.
Invariant table
- Ask AI to build a table with iteration number, condition value, state before, state after and exit reason.
- Use the table to identify zero-iteration and infinite-loop risks.
- Keep the table short: first few iterations plus the exit case are usually enough.
Assessment tasks
- Rewrite one
while(true)loop as a condition-driven loop and explain the exit condition. - Write one
whileand onedo-whileexample that show the zero-times versus at-least-once difference. - Create a termination audit for an input loop with successful input, sentinel input and failed input.
- Refactor one
while(!stream.eof())loop into a read-as-condition loop and explain the stale-value bug. - Build one menu loop with
do-while, including a quit sentinel and failed-input guard. - Add a watchdog counter and a deadline to one polling loop, then explain each exit path.
- Write a nested file/token reading loop and identify the progress point for each level.
- Repair one token-reading
while(true)loop so EOF and malformed input cannot reuse stale state. - Compare loop choice for file reading, menu prompting and fixed-count traversal.
Judgement questions
Why is non-deterministic iteration powerful but risky?
Discuss unknown counts, external state and termination proof.
When does do-while communicate intent better than while?
Use a menu, retry or first-attempt example.
What should you ask every time you review a while loop?
Mention invariant, changing state and exit condition.
