Control Flow
if, switch, and three loops — plus two escape hatches, break and continue, and the infamous goto. The condition of every decision is any integer expression: zero is false, everything else is true.
if / else — Branching
The if statement executes a block when its condition is true; the optional else runs otherwise. Chain several conditions with else if. Two pitfalls to internalize: a dangling else binds to the nearest if, and a stray ; after the condition turns the branch into an empty statement.
Figure 1 — the if decision: test once, take exactly one path (shared control-flow primitive).
int grade = 87;
if (grade >= 90) {
printf("A\n");
} else if (grade >= 80) { // checked only when the first test failed
printf("B\n");
} else {
printf("below B\n");
}
Remember the assignment trap: if (x = 5) assigns and is always true. Enable -Wparentheses to make the compiler flag this classic.
switch — Many-Way Selection
When one integer is compared against many constants, switch reads better than a chain of ifs — and compilers can turn it into a jump table. Execution falls through from one case to the next, so every case body must end with break unless you deliberately want fall-through (rare — comment it).
char grade = 'B';
switch (grade) {
case 'A':
printf("excellent\n");
break; // without this, the 'B' case would also run
case 'B':
printf("good\n");
break;
case 'C':
case 'D': // shared body: fall through deliberately
printf("passing\n");
break;
default: // any other value
printf("unknown\n");
break;
}
while & do-while — Repetition
while (cond) tests before each iteration — the body may never run. do { ... } while (cond) tests after, guaranteeing at least one iteration — the right shape for menu prompts and retry loops. Both loops stop as soon as the condition is false.
Figure 2 — while: pre-test loop.
Figure 3 — do-while: post-test loop.
#include <stdio.h>
int main(void) {
int tries = 0;
int attempts; // a pretend server answer
do {
tries++; // the body runs at least once
attempts = (tries < 3) ? -1 : 200; // fail twice, then succeed
printf("attempt %d\n", tries);
} while (attempts != 200); // keep looping until a 200 arrives
return 0;
}
for — Counted Repetition
The for loop bundles three clauses inside one header: initialization, condition, and the step run after each iteration. The classic diagram is the three-clause cycle that every C programmer sees in their sleep.
Figure 4 — for (init; cond; step): init once, test, run body, step, test again (shared control-flow primitive).
#include <stdio.h>
int main(void) {
// sum of squares from 1 to n — the three clauses in one header
long sum = 0;
for (int i = 1; i <= 10; i++) {
sum += (long)i * i; // cast one factor to avoid int overflow
}
printf("sum of squares 1..10 = %ld\n", sum); // 385
return 0;
}
break exits the nearest loop immediately; continue jumps to the next iteration's step. Use them to abandon work early — scanning for a match, skipping invalid rows — but keep loop bodies readable.
goto — With Judgment
goto jumps to a label. It is derided because unrestricted jumps make programs unreadable — but in C it has two legitimate, even idiomatic uses: breaking out of deeply nested loops (C has no labeled break), and centralized error cleanup in one function. Everywhere else, prefer structured flow.
#include <stdio.h>
int main(void) {
// search a pretend grid; jumping out of nested loops in one step
for (int r = 0; r < 10; r++) {
for (int c = 0; c < 10; c++) {
if (r * 10 + c == 42) {
goto found; // legitimate: multi-level exit
}
}
}
printf("not found\n");
return 1;
found:
printf("found 42\n");
return 0;
}
Composed Example — FizzBuzz
FizzBuzz uses every construct on this page at least once: a counted loop, a modulo test, nested decisions, and a default branch. It is the interview filter that shows whether structured control flow is automatic.
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 30; i++) {
if (i % 15 == 0) { // divisible by 3 AND 5
printf("FizzBuzz\n");
} else if (i % 3 == 0) {
printf("Fizz\n");
} else if (i % 5 == 0) {
printf("Buzz\n");
} else {
printf("%d\n", i);
}
}
return 0;
}
Next: input & output — reading and writing the world around the program.