C Part I · Module 4 · Decisions and Control Flow
break and continue
Every loop so far has had exactly one way out: the condition. That is a clean structure, and it is often the wrong shape for the problem. A search knows it is finished the moment it finds what it was looking for, which is in the middle of the body rather than at the top. A filter knows a particular item is uninteresting before it has done any of the work the rest of the body does. C has a statement for each situation. They are two lines of syntax and a surprising amount of consequence, because one of them means something materially different depending on which kind of loop it appears in — and the difference is between a loop that works and a loop that never finishes, with nothing from the compiler to tell you which one you wrote.
Foundation16 min readCPart IFundamentalsControl Flow
Part I · Module 4 · Chapter 4.6 · break and continue
1. Leaving Early
A search is the clearest case. You are looking for the smallest divisor of a number; the moment you find one, every remaining candidate is wasted work.
#include <stdio.h>
int main(void)
{
unsigned int value = 91u;
unsigned int smallest_divisor = 0u;
for (unsigned int d = 2u; d < value; d++) {
if (value % d == 0u) {
smallest_divisor = d;
break;
}
}
if (smallest_divisor == 0u) {
printf("%u has no divisor below itself other than 1\n", value);
} else {
printf("smallest divisor of %u is %u\n", value, smallest_divisor);
}
return 0;
}Output:
smallest divisor of 91 is 7break ends the loop immediately. The condition is not evaluated again, the advance clause does not run, and execution continues at the first statement after the loop.
Two details of that program are the normal way to write a search, and both are worth copying. The result variable is initialised to a value that cannot be a real answer, so the code after the loop can tell the difference between found 0 and found nothing. And the decision about what to report is made after the loop, not inside it, which keeps the loop body about searching.
2. Skipping One Pass
continue abandons the rest of the body and moves on to the next pass. The loop itself keeps going.
#include <stdio.h>
int main(void)
{
int considered = 0;
int processed = 0;
for (int i = 0; i < 6; i++) {
considered = considered + 1;
if (i % 2 == 0) {
continue;
}
processed = processed + 1;
printf("processing %d\n", i);
}
printf("considered %d, processed %d\n", considered, processed);
return 0;
}Output:
processing 1
processing 3
processing 5
considered 6, processed 3Six passes, three of which reached the bottom of the body. The counter printed before the continue incremented every time; the counter printed after it incremented only on the passes that got that far. That is the whole behaviour: statements above the continue run, statements below it do not.
3. continue Means Different Things in the Two Loop Forms
Chapter 4.5 said that for and while are close enough to express each other, and then said there was one statement for which that is not quite true. This is it.
continuemoves to the next iteration. In aforloop, the next iteration begins with the advance clause. In awhileloop, it begins with the condition.
In a for loop that distinction costs nothing, because the advance lives in the header and continue cannot skip it. §2's program proves it: i went 0, 1, 2, 3, 4, 5 and the loop ended, even though half the passes were abandoned partway through.
In a while loop the progress statement lives in the body — and continue skips the rest of the body.
int i = 0;
while (i < 5) {
if (i % 2 == 0) {
continue;
}
printf("%d\n", i);
i = i + 1;
}Trace it. i holds 0, the condition is true, the body begins. 0 % 2 == 0 is true, so continue runs. Control goes to the condition. i still holds 0, because the statement that would have incremented it is below the continue and never ran. The condition is true again. And so on, forever.
Compiled with -Wall -Wextra -Wpedantic, this produces no diagnostic at all. It is the same category of failure as Chapter 4.4's body-with-no-progress, arrived at by a different route: every individual statement is reasonable, and the loop makes no progress on the path the continue takes.
The repair is to make the progress happen before anything can skip it:
#include <stdio.h>
int main(void)
{
int i = 0;
while (i < 5) {
i = i + 1;
if (i % 2 == 0) {
continue;
}
printf("while body reached %d\n", i);
}
printf("while finished with i = %d\n", i);
return 0;
}Output:
while body reached 1
while body reached 3
while body reached 5
while finished with i = 54. break Leaves Exactly One Level
break ends the innermost loop containing it. Not all of them. This surprises people the first time a search is nested:
#include <stdio.h>
int main(void)
{
int target = 12;
int found_a = 0;
int found_b = 0;
for (int a = 1; a <= 4; a++) {
for (int b = 1; b <= 4; b++) {
printf("trying %d x %d\n", a, b);
if (a * b == target) {
found_a = a;
found_b = b;
break;
}
}
}
printf("last pair found: %d x %d\n", found_a, found_b);
return 0;
}Output:
trying 1 x 1
trying 1 x 2
trying 1 x 3
trying 1 x 4
trying 2 x 1
trying 2 x 2
trying 2 x 3
trying 2 x 4
trying 3 x 1
trying 3 x 2
trying 3 x 3
trying 3 x 4
trying 4 x 1
trying 4 x 2
trying 4 x 3
last pair found: 4 x 3The program found 3 x 4 on the twelfth attempt and reported 4 x 3. The break ended the inner loop, the outer loop advanced a to 4, the inner loop started again from b = 1, and the second match overwrote the first. The answer printed is the last pair found, not the first one — and if the intent was to stop at the first match, this program is simply wrong.
The usual repair with the tools you have is a flag that both loops test:
int done = 0;
for (int a = 1; a <= 4 && !done; a++) {
for (int b = 1; b <= 4; b++) {
if (a * b == target) {
found_a = a;
found_b = b;
done = 1;
break;
}
}
}That works, and it is what most code does. It also adds an object whose only purpose is to carry a decision between two loops, and the outer condition now has two unrelated jobs. There is a construct that leaves both loops directly, and it is the subject of Chapter 4.8 — which exists largely because this situation is common enough to need an honest answer.
5. break Inside a switch Inside a Loop
Chapter 4.3 introduced break as the statement that leaves a switch. This chapter introduces it as the statement that leaves a loop. Put a switch inside a loop and the obvious question becomes urgent: which one does it leave?
breakapplies to the innermost enclosingswitchor loop, whichever is nearer.
So inside a switch that is inside a loop, break leaves the switch:
#include <stdio.h>
int main(void)
{
for (int code = 0; code < 4; code++) {
printf("code %d: ", code);
switch (code) {
case 2:
printf("terminator seen, leaving... ");
break;
default:
printf("ordinary ");
break;
}
printf("(still in the loop)\n");
}
printf("after the loop\n");
return 0;
}Output:
code 0: ordinary (still in the loop)
code 1: ordinary (still in the loop)
code 2: terminator seen, leaving... (still in the loop)
code 3: ordinary (still in the loop)
after the loopThe case that announced it was leaving did not leave. It left the switch, the printf after the switch ran as usual, and the loop went on to code 3.
This is one of the quieter bugs in C, because the code reads exactly as though it works. The usual fix is the same flag as §4 — set it in the case, and test it in the loop condition or immediately after the switch.
6. A Realistic Loop Using Both
Scanning a status word for set bits is a loop where both statements do real work: uninteresting bits are skipped, and one particular bit means abandon the scan entirely.
#include <stdio.h>
int main(void)
{
const unsigned int status = 0xB4u;
const unsigned int fatal_mask = 0x80u;
int reported = 0;
int skipped = 0;
for (int bit = 0; bit < 8; bit++) {
unsigned int mask = 1u << bit;
if ((status & mask) == 0u) {
skipped = skipped + 1;
continue;
}
if (mask == fatal_mask) {
printf("bit %d is fatal — abandoning the scan\n", bit);
break;
}
printf("bit %d is set\n", bit);
reported = reported + 1;
}
printf("reported %d, skipped %d\n", reported, skipped);
return 0;
}Output:
bit 2 is set
bit 4 is set
bit 5 is set
bit 7 is fatal — abandoning the scan
reported 3, skipped 40xB4 is 1011 0100, so bits 2, 4, 5 and 7 are set, and the four clear bits below bit 7 were skipped. Both statements are expressing something the condition at the top of the loop could not: this particular pass has nothing to do, and there is no point continuing at all.
Notice that reported + skipped is 7, not 8. Bit 7 was neither reported nor skipped, because the break left before either counter was touched. When a loop can exit early, the totals it leaves behind describe what it got through, not what it was asked to examine — and code after the loop that assumes otherwise is a bug waiting for the right input.
7. Using Them Well
Both statements add an exit path, and exit paths are what make a loop hard to reason about. Two habits keep that cost down.
Put the early exits at the top of the body. A body that reads skip this case, skip that case, now do the work is easy to follow. A continue buried below thirty lines of processing is not, because the reader has to hold everything above it in mind to know what was already done.
Keep the loop's invariant true at every exit. Whatever the loop promises — a counter that matches the number of items handled, a total that includes everything accepted — must still hold when break or continue leaves. §6 illustrates the point: the counters are honest about the early exit precisely because nothing was half-updated when it happened.
Used that way, both statements make loops simpler rather than more complex. They remove the flag variables and nested conditions you would otherwise need to express stop and skip, and those have their own costs — §4 and §5 are both, in the end, about a flag standing in for a jump the language would not make directly.
8. What You Should Be Able to Say Now
breakends the enclosing loop immediately — no further condition test, no advance.continueabandons the rest of the body and goes on to the next iteration.- Statements above a
continuerun; statements below it do not run on that pass. - In a
forloop,continuereaches the advance clause, so the control variable still changes. - In a
whileloop,continuegoes straight to the condition — a progress statement below it is skipped and the loop never ends. - That broken
whileproduces no compiler diagnostic. It is Chapter 4.4's no-progress bug by another route. breakleaves exactly one level; a nested search keeps running the outer loop.breakapplies to the innermost enclosingswitchor loop — so inside aswitchinside a loop, it leaves theswitch.- Early exits belong at the top of the body, where the reader meets them first.
- After an early exit, counters and totals describe what the loop got through.
9. Exercises
Exercise 1 — predict the output. Do not compile yet. Give the exact output of each.
for (int i = 0; i < 5; i++) {
if (i == 3) {
break;
}
printf("%d ", i);
}
printf("| done\n");for (int i = 0; i < 5; i++) {
if (i == 3) {
continue;
}
printf("%d ", i);
}
printf("| done\n");Exercise 2 — count the passes. For each loop in Exercise 1, say how many times the body began and how many times it reached the final printf inside the loop.
Exercise 3 — find the loop that never ends. One of these finishes and one does not. Say which, explain it in terms of §3, and give the smallest repair.
int n = 0;
while (n < 4) {
n = n + 1;
if (n == 2) {
continue;
}
printf("%d\n", n);
}int n = 0;
while (n < 4) {
if (n == 2) {
continue;
}
printf("%d\n", n);
n = n + 1;
}Exercise 4 — convert and compare. Rewrite the working loop from Exercise 3 as a for loop. Then say whether moving the continue above the progress statement would break the for version, and why.
Exercise 5 — explain the wrong answer. §4's nested search printed 4 x 3 although it found 3 x 4 first. Explain exactly which loop the break ended and what happened next. Then say what the program would print for a target of 16.
Exercise 6 — find the break that does nothing useful. This is meant to stop the loop at the first error code. Say what it actually does, and give two repairs — one using a flag and one restructuring the test.
for (int i = 0; i < 5; i++) {
switch (i % 3) {
case 0:
printf("ok\n");
break;
default:
printf("error, stopping\n");
break;
}
}Exercise 7 — audit the totals. In §6's scan, reported + skipped is 7 rather than 8. Explain why, and say what would have to change for the counters to account for all eight bits.
Exercise 8 — place the exits. Take §6's loop and move the fatal-bit test above the clear-bit test. Say what changes in the output and whether the program is still correct, then say which ordering you would ship and why.
10. Summary
breakends the enclosing loop immediately;continueabandons the rest of the body and moves to the next pass.- In a
forloop,continuestill reaches the advance clause. In awhileloop it does not — it goes straight to the condition. - A
whileloop whose progress statement sits below acontinuenever finishes, and the compiler says nothing. breakleaves one level only, which is why nested searches need a flag — or Chapter 4.8's construct.breakbinds to the innermostswitchor loop, so abreakinside aswitchinside a loop leaves only theswitch.- Put early exits at the top of the body, and make sure the loop's promises still hold when one is taken.
Four chapters of control flow are now in place, and two of them have produced a loop that runs forever with nothing from the compiler to warn about it. That is not a coincidence, and it is not a reason for more careful typing. A small number of mistakes account for a large share of all loop bugs ever written, they have recognisable shapes, and the habits that prevent them are specific rather than general. The next chapter names them.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 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.
