Control Flow

Programs make decisions and repeat work. C offers the classic structured trio — 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.

Decision diamond splitting true and false paths

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.

While loop: test condition before each iteration

Figure 2 — while: pre-test loop.

Do-while loop: body runs before the test

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.

Classic for loop: init, test, body, step cycle

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.