Masters-level programming lessons for professional software judgement.

Masters Programmes · Masters Software Engineering · Lesson 11

While and Do-While Loops: Non-Deterministic Iteration

Use `while` and `do-while` loops when the iteration count is not known in advance, and reason carefully about state change and termination.

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.

LevelMasters
ModeSeminar, state tracing and loop termination review
EvidenceA loop choice audit with invariants, termination conditions and a repaired condition-driven loop
SourceConverted from supplied Canvas lesson HTML

Learning objectives

  • Distinguish while from do-while in terms of first-condition timing and minimum execution count.
  • Choose for, while or do-while based 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.

W1

Choose by entry knowledge

for fits known counts; while and do-while fit conditions discovered during execution.

W2

Know zero vs one

while may execute zero times; do-while executes at least once.

W3

Make progress visible

Some state must change each iteration so the condition can eventually become false.

W4

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

  1. while checks x < 5 before entering the body.
  2. If the initial condition is false, the while body executes zero times.
  3. do-while executes the body first, then checks the condition.
  4. Both loops rely on ++x, --y or another explicit state update to move state toward termination.
Line or operationVariableValueState roleInvariant or check
Read the example before running itKey names and callsKnown from sourceReasoning setupEach named element should support the lesson focus: While and Do-While Loops: Non-Deterministic Iteration.
Trace the first meaningful operationPrimary state or boundaryEstablished by initialisation, call or conditionValid-state checkpointLater reasoning must not use the value before this checkpoint.
Identify the first decision or dereferenceControl or access pointDepends on current stateFailure-mode checkpointThe condition, pointer, argument or dependency must be valid before use.
Record the observable resultReturn value, output or mutationProduced by the exampleEvidence checkpointThe 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 state
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. 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-while loop 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_clock deadline.
  • 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 quit as 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-while menu 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 switch and the loop condition.
Add safety valves
  • Ask AI to add a watchdog counter or steady_clock deadline to an existing loop.
  • Ask it to document each failure mode and what each break means.
  • 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 continue paths 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, while or do-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

  1. Rewrite one while(true) loop as a condition-driven loop and explain the exit condition.
  2. Write one while and one do-while example that show the zero-times versus at-least-once difference.
  3. Create a termination audit for an input loop with successful input, sentinel input and failed input.
  4. Refactor one while(!stream.eof()) loop into a read-as-condition loop and explain the stale-value bug.
  5. Build one menu loop with do-while, including a quit sentinel and failed-input guard.
  6. Add a watchdog counter and a deadline to one polling loop, then explain each exit path.
  7. Write a nested file/token reading loop and identify the progress point for each level.
  8. Repair one token-reading while(true) loop so EOF and malformed input cannot reuse stale state.
  9. 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.