C++ Part I · Module 5 · Control Flow
break, continue and Loop Correctness
Every loop in this module has run its body from top to bottom and tested its condition at the appointed moment. Real loops often want neither. Sometimes the answer is found halfway through the body and there is no reason to finish, let alone iterate again; sometimes one item should be passed over without ending the traversal. C++ has a statement for each, and both are easy to use correctly once you know the one thing that is not obvious about them. That one thing is where this chapter earns its place. Chapter 5.4 established that a loop is a condition plus a guarantee that the body makes progress toward the condition becoming false, and continue is the statement that can quietly void the second half of that. In a for loop it is harmless, because the advance lives in the header and runs regardless. In a while loop the advance is an ordinary statement in the body, and skipping it means skipping the only thing that was going to end the loop. The chapter then closes the module on the two bugs that account for most loop failures, and on a measurement that is worth knowing for its own sake: whether your compiler warns about one of the most common of them turns out to depend on which line you put a semicolon on.
Foundation17 min readC++Part IFundamentalsControl Flow
Part I · Module 5 · Chapter 5.7 · break, continue and Loop Correctness
1. Two Statements That Change the Normal Flow
break; // leave the loop entirely
continue; // abandon this iteration, go on to the nextBoth are single words with no operands, and both act on the innermost enclosing loop.
They exist because the natural shape of a loop — test, run the whole body, test again — is not always the shape of the problem. A search knows the answer before the collection runs out. A filter has nothing to do with some items. Writing either of those without break or continue is possible and usually involves an extra flag variable that exists only to carry the decision to the bottom of the body.
2. break: Leave Now
break ends the loop immediately. Nothing else in the body runs, the condition is not re-tested, and execution continues after the loop:
for (int i = 0; i < n; ++i) {
if (match(i)) {
found = i;
break; // no reason to look at the rest
}
}Chapter 5.4 §7 already relied on this: for (;;) with a break is the well-defined way to write a deliberately unbounded loop, and the break is what gives it an exit.
break leaves exactly one loop. In nested loops it is the inner one:
#include <iostream>
int main()
{
for (int r = 0; r < 3; ++r) {
for (int c = 0; c < 3; ++c) {
if (c == 1) break; // leaves the inner loop only
std::cout << "r" << r << "c" << c << ' ';
}
}
std::cout << '\n';
return 0;
}Expected output:
r0c0 r1c0 r2c0The inner loop was cut short three separate times — once per outer iteration — and the outer loop ran to completion. There is no break 2; in C++. To leave both, the usual answers are a flag tested by the outer condition, or moving the nested loop into a function and using return, which Chapter 6.3 makes the cleaner option.
A reminder from Chapter 5.3: inside a switch, break ends the switch, not an enclosing loop. A switch inside a loop therefore needs care — the break that ends a case does not continue the loop's iteration or leave it.
3. continue: Skip the Rest of This Iteration
continue abandons the remainder of the body and goes on to the next iteration:
#include <iostream>
int main()
{
for (int i = 0; i < 5; ++i) {
if (i == 2) continue; // the ++i still happens
std::cout << "i = " << i << '\n';
}
return 0;
}Expected output:
i = 0
i = 1
i = 3
i = 4i == 2 was skipped and the loop finished normally. That comment is the most important one in the chapter: in a for loop, continue still runs the advance, because the advance is in the header and the header is not part of the body.
4. The Same continue in a while Loop Never Ends
Now put the identical logic in a while loop, where the advance is an ordinary statement:
#include <iostream>
int main()
{
int i = 0, guard = 0;
while (i < 5) {
if (i == 2) {
if (++guard > 3) {
std::cout << "stuck at i=" << i << ", stopped by a guard\n";
break;
}
continue; // skips ++i -- nothing advances
}
std::cout << "i = " << i << '\n';
++i;
}
return 0;
}Expected output:
i = 0
i = 1
stuck at i=2, stopped by a guardWithout the guard this loop runs forever. i reaches 2, the continue jumps straight back to the condition, i is still 2, the condition is still true, and the body makes the same decision again. Nothing in the program ever changes.
This is not Chapter 5.4's undefined kind of infinite loop. The body does observable work — it prints — so the forward-progress assumption does not apply and the behaviour is perfectly defined. It simply never finishes.
Chapter 5.4 §4 asked three questions of every loop. The second one was "does the progress statement always run?" — and
continueis the statement that makes the answer no.
Three ways to write it correctly, in the order you should prefer them:
Use a for loop. The advance moves into the header, where continue cannot skip it. This is the strongest argument for preferring for whenever the stepping is regular.
Advance before the continue.
while (i < 5) {
const int current = i;
++i; // advance first, unconditionally
if (current == 2) continue;
std::cout << "i = " << current << '\n';
}Invert the condition instead of continuing.
while (i < 5) {
if (i != 2) std::cout << "i = " << i << '\n';
++i;
}The third is usually the clearest for a single skip. continue earns its place when the body is long and several conditions each disqualify an item — turning one deeply nested if into a short series of guard clauses at the top.
5. continue in a do-while Goes to the Condition
One more behaviour, because it surprises people and because the surprise is not what they expect:
#include <iostream>
int main()
{
int i = 0, body_entries = 0;
do {
++body_entries;
++i;
if (i < 3) continue; // jumps straight to the while(...) test
std::cout << "reached the tail at i = " << i << '\n';
} while (i < 5);
std::cout << "body entered " << body_entries << " times, final i = " << i << '\n';
return 0;
}Expected output:
reached the tail at i = 3
reached the tail at i = 4
reached the tail at i = 5
body entered 5 times, final i = 5The continue does not restart the body — it jumps to the condition at the bottom, which is then tested as normal. So the loop terminated correctly here: the advance was before the continue, which is §4's second remedy applied by accident.
What it did do is skip the tail of the body on the first two iterations. The body was entered five times and the tail was reached three. In a do-while the condition is below the code it governs, so continue jumps forward rather than back, and a reader scanning for "where does continue go?" has to look down instead of up.
This is one more reason Chapter 5.4 §3 asked you to use do-while rarely.
C++ does have one more jump statement, goto, which can leave several loops at once. It is mentioned here only so you recognise it; essentially every use of it in modern C++ is better written another way, and nothing in this curriculum needs it.
6. The First Bug: Off-By-One
The two bugs this chapter closes on account for most loop failures, and both have already been measured in this module.
Off-by-one is a loop that runs one iteration too many or too few. Chapter 5.5 §5 measured the plain version: i <= n runs n + 1 times where i < n runs n.
The reliable fix is the convention from that chapter rather than care: start at zero, stop before the count. The half-open range makes the length equal the bound, so there is no + 1 or - 1 anywhere to get backwards.
Two habits catch the rest:
Check the ends, never the middle. For a loop over n items, trace n = 0, n = 1, and the last iteration. The middles always work. A loop that is right at zero, right at one, and right at the top is right.
Count the iterations, not the comparisons. Ask "how many times does the body run?" and compare it to the number you wanted. That is a question with one answer, where "is <= right here?" is a question about punctuation.
The worst instance in this module was Chapter 5.5 §6's countdown, where the off-by-one at the bottom end did not produce one extra iteration — it produced 4294967295 and a loop that could not terminate, because the index was unsigned.
7. The Second Bug: The Loop That Never Ends
Three distinct causes have appeared, and they are worth separating because the fixes differ:
| Cause | Where | Fix |
|---|---|---|
| No progress statement at all | 5.4 §5 | write one; and note it was undefined, trapping at -O2 |
| The condition can never be false | 5.5 §6 | i >= 0 on an unsigned index — use a signed type |
| The progress statement is skipped | §4 above | use for, or advance before the continue |
One checklist covers all three, and it is Chapter 5.4 §4's, with the third item now sharpened:
- Name the statement that makes the condition false. If you cannot, stop writing and fix that.
- Can the condition be false at all? An unsigned value is never below zero.
- Does that statement run on every path through the body? Check every
continueand every early branch.
8. Where the Compiler Will and Will Not Help
Three mistakes, three different answers — and the third is the one worth remembering.
A stray semicolon after the header. The loop gets an empty body and the code you meant as the body runs once, after the loop:
for (int i = 0; i < 5; ++i); t += 1;warning: for loop has empty body [-Wempty-body]Now the same mistake with the semicolon at the end of its own line:
for (int i = 0; i < 5; ++i);
t += 1;$ c++ -std=c++17 -Wall -Wextra -Wpedantic stray.cpp -o stray
$ ← zero warningsIdentical meaning, different diagnostic. Both loops have an empty body and both run t += 1 exactly once. The only difference is which line the semicolon sits on.
That is deliberate, and the note attached to the warned version says so: put the semicolon on a separate line to silence this warning. An empty loop body is occasionally intended, and clang treats a semicolon alone on its line as the way you write one on purpose. So the warning is not reporting "this body is empty" — it is reporting "this body is empty and you do not appear to have meant it", and the evidence it uses is your layout.
Misleading indentation. Here the compiler does well:
#include <iostream>
int main()
{
int total = 0;
for (int i = 0; i < 5; ++i)
std::cout << "counting " << i << '\n';
total += 1; // indented, but NOT in the loop
std::cout << "total = " << total << '\n';
return 0;
}indent.cpp:8:9: warning: misleading indentation; statement is not part of the previous 'for' [-Wmisleading-indentation]Expected output:
counting 0
counting 1
counting 2
counting 3
counting 4
total = 1total is 1, not 5. The loop body was the single std::cout statement, and total += 1; ran once after the loop finished. Chapter 5.1 §2 asked for braces unconditionally; this is the second of the two bugs it promised, and it is why.
An empty if body is caught too — if (n > 0); gives warning: if statement has empty body [-Wempty-body].
9. What to Actually Do
Use break for a search that has found its answer, and continue for items that do not apply.
Prefer for over while when the stepping is regular — the advance is then in a slot continue cannot skip.
If you continue in a while loop, advance before it, unconditionally.
Use continue for guard clauses, when several conditions each disqualify an item; invert the condition instead for a single skip.
Remember break leaves one loop. For two, use a flag or move the loops into a function and return.
Remember break in a switch ends the switch, not the enclosing loop.
Count up and stop before the bound, and trace n = 0, n = 1 and the last iteration.
Write braces always. §8's indentation bug and Chapter 5.1 §7's dangling else both disappear.
Add -Wimplicit-fallthrough and -Wtautological-unsigned-zero-compare to your build.
10. Mistakes Worth Making Once
continue in a while loop before the advance. Measured: stuck at i = 2 forever, with zero warnings.
Assuming continue behaves the same in for and while. Measured: the for version printed 0, 1, 3, 4 and terminated.
Expecting continue in a do-while to restart the body. It jumps to the condition at the bottom.
Expecting break to leave two loops. Measured: r0c0 r1c0 r2c0 — the outer loop ran to completion.
Confusing a switch's break with a loop's. The inner construct wins.
A stray semicolon after a loop header. Measured: warned on one line, silent on two.
Trusting indentation. Measured: total = 1, not 5 — though here the compiler does warn.
Assuming -Wall -Wextra is enough. Four of this module's measured bugs are outside it.
11. Interview Relevance
"What is the difference between break and continue?" is a warm-up: one leaves the loop, one skips to the next iteration. The follow-up is the real question.
"Is continue ever dangerous?" Yes, and the answer is specific: in a while or do-while loop, continue skips the remainder of the body including any statement that was advancing the loop, so a loop that looks correct can run forever. In a for loop it cannot, because the advance is in the header. Saying which loop forms are affected and why is what separates a memorised answer from an understood one.
"How do you exit two nested loops?" There is no break 2;. Name both options — a flag the outer condition tests, or extracting the loops into a function and using return — and prefer the second.
12. What You Should Be Able to Say Now
breakleaves the loop;continueabandons the current iteration. Both act on the innermost enclosing loop.- Measured:
breakin a nested loop gaver0c0 r1c0 r2c0— the outer loop ran to completion. There is nobreak 2;. - Inside a
switch,breakends the switch, not an enclosing loop. - In a
forloop,continuestill runs the advance. Measured: 0, 1, 3, 4, and it terminated. - In a
whileloop,continueskips the advance. Measured: stuck ati = 2forever, zero warnings. - That loop is well-defined, not Chapter 5.4's undefined kind, because its body prints — it simply never finishes.
- Three fixes, in order: use a
for, advance before thecontinue, or invert the condition. - In a
do-while,continuejumps to the condition at the bottom — measured: body entered 5 times, tail reached 3. - Off-by-one:
i <= nrunsn + 1times. Fix it with the half-open convention, and checkn = 0,n = 1and the last iteration. - The never-ending loop has three causes: no progress statement (5.4, and undefined), a condition that can never be false (5.5's unsigned index), and a progress statement that gets skipped (§4).
- Measured, and the finding worth keeping: a stray semicolon after a loop header warns on one line and is silent on two — the diagnostic depends on layout, because an empty body is sometimes intended.
- Measured: misleading indentation is warned, and
totalwas 1 rather than 5. - The module's uncaught bugs are exactly those whose deliberate use is spelled identically — fallthrough, unbounded loops, unsigned indices, by-value loop variables.
- So: enable
-Wimplicit-fallthroughand-Wtautological-unsigned-zero-compare, and for the rest the discipline is the tool.
13. Exercises
Exercise 1 — trace the skip. Give the output of §3's for loop and of §4's while loop without its guard, and name the one line that accounts for the difference.
Exercise 2 — fix it three ways. Repair §4's loop so it prints i = 0, 1, 3, 4 and terminates, using each of the three remedies. Say which you would ship and why.
Exercise 3 — where does continue go? For a for, a while and a do-while, say exactly what runs next after a continue, and which of the three can therefore skip a progress statement.
Exercise 4 — leave both loops. Write a nested loop that stops entirely on the first match, two ways: with a flag, and by extracting a function. Say which reads better.
Exercise 5 — which break? In a loop containing a switch containing a break, say what that break ends, and write the code that leaves the loop from inside the switch.
Exercise 6 — predict the diagnostic. For each, say whether -Wall -Wextra -Wpedantic warns, and give the text where it does: (a) for (int i = 0; i < 5; ++i); t += 1; on one line (b) the same across two lines (c) if (n > 0); (d) a loop body indented but unbraced, with a second statement (e) a switch case missing its break.
Exercise 7 — name the cause. For each never-ending loop, say which of §7's three causes it is and give the fix: (a) while (busy) { } (b) for (unsigned i = n; i >= 0; --i) (c) while (i < n) { if (skip(i)) continue; ++i; } (d) while (n != 1) { n -= 2; } with n starting at 10.
14. Summary
breakleaves the loop;continueskips the iteration; both act on the innermost loop.- Measured:
breakin nested loops gaver0c0 r1c0 r2c0. There is nobreak 2;. - In a
switch,breakends the switch. continuein aforstill runs the advance (measured: 0, 1, 3, 4) and in awhileit does not (measured: stuck forever, zero warnings).- That stuck loop is well-defined — it prints, so it is observable — and it simply never ends.
- In a
do-while,continuejumps down to the condition — body entered 5 times, tail reached 3. - Off-by-one:
i <= nrunsn + 1times; the half-open convention removes the arithmetic. - Three causes of a never-ending loop: no advance, a condition that cannot be false, an advance that is skipped.
- Measured: a stray semicolon warns on one line and is silent on two; misleading indentation is warned, and
totalwas 1. - The bugs
-Wall -Wextramisses are exactly those whose deliberate use is spelled the same way — so enable the extra flags and let discipline cover the rest.
Module 5 is finished. With Module 3's types and Module 4's expressions, you can now write a complete program: it can decide, it can repeat, and you can say why each of its loops terminates. That is enough to solve a real problem, and it is also the point at which a program written as one long main stops being workable — because every name is visible to every line, every piece of logic is reachable from every other, and nothing can be tested on its own.
Module 6 is the answer, and its first job is smaller than it sounds. A function is not primarily a way to avoid repeating yourself; it is a way to make a contract — this goes in, that comes out — so that a reader can use it without reading it. The module establishes one detail completely rather than quickly, because almost everything in Modules 8, 12 and 13 depends on the reader having it exactly right: when you call a function, the argument is copied into the parameter. What that means for an int is obvious. What it means for everything else is most of the rest of this curriculum.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
