Macro photograph of gears from a pocket-watch movement, lit warmly. Photo · Laura Ockel / Unsplash
A loop is a gear meshing with itself. Tireless. Identical revolution after revolution. The chef has all the time in the universe.

Computers are dazzlingly fast. They are also dazzlingly patient. A modern CPU does three or four billion operations per second and will gladly do them, in a straight repetitive line, until you tell it to stop. The construct that gives you that stamina, in any language you'll ever use, is a loop.

If if is the atom of decision, the loop is the atom of repetition. Together, they are computationally enough — Turing's tape machine had nothing else, and could in principle do everything every modern program does. C gives you three flavours of loop. Almost every other language gives you the same three, with cosmetic variations.

The three flavours

while — repeat as long as a condition is true

int i = 0;
while (i < 5) {
    printf("%d\n", i);
    i++;
}

Check the condition. If true, run the body. Check again. If still true, run the body again. Continue until the condition is false. while is the most general loop in C — every other loop can be written as a while.

for — when you know how many times

for (int i = 0; i < 5; i++) {
    printf("%d\n", i);
}

Three pieces, separated by semicolons inside the parentheses: initialiser, condition, step. The initialiser runs once before the loop starts; the condition is checked at the start of each iteration; the step runs at the end of each iteration. It's exactly equivalent to the while above — same machine code, different shape — but signals intent: "I'm walking through a fixed range of values".

do { } while — at least one iteration, guaranteed

do {
    printf("keep going? (y/n): ");
    scanf(" %c", &c);
} while (c == 'y');

Same as while, but the condition is checked after the body runs. Use it when you must do the body once before you can decide whether to continue (reading user input, or trying an operation that might succeed or fail).

What the chef sees

Every loop, no matter the syntax, compiles down to roughly the same shape — a couple of labels and a backward jump:

// for (int i = 0; i < 5; i++) { ...body... }

        mov   w0, #0          // i = 0
.loop_top:
        cmp   w0, #5          // compare i to 5
        b.ge  .loop_end      // if i >= 5, exit
        // ... body ...
        add   w0, w0, #1      // i++
        b     .loop_top      // jump back
.loop_end:
        // continue with the rest of the program

One cmp, one conditional branch, one body, one increment, one unconditional branch back to the top. That's the entire loop machinery, repeated however many times the chef needs to. Five iterations — five trips around. Five million — five million trips. The chef does not get tired. The chef does not get bored.

condition? body step (++) exit true false loop back

The classic patterns

Almost every loop you'll ever write fits one of these patterns. Recognise them and they fall off your fingers without thinking:

Walking an array

for (int i = 0; i < n; i++) {
    total += scores[i];
}

Searching for a value

int found_at = -1;
for (int i = 0; i < n; i++) {
    if (arr[i] == target) {
        found_at = i;
        break;           // exit the loop now
    }
}

Reading until end-of-input

int c;
while ((c = getchar()) != EOF) {
    putchar(c);
}

This last one is straight out of K&R. It reads characters from stdin and writes them to stdout until end-of-file. Idiomatically, that single line — (c = getchar()) != EOF — assigns the next character into c, then compares the result to EOF, all in one expression. C tolerates this kind of compactness; it has trade-offs in readability that you should consider for yourself.

break and continue

Two keywords that change the flow inside a loop:

// print only the even numbers from 0 to 19, but stop at 12
for (int i = 0; i < 20; i++) {
    if (i == 12) break;      // stop entirely
    if (i % 2 != 0) continue; // skip this iteration
    printf("%d\n", i);
}

This prints 0 2 4 6 8 10, then breaks out at 12. continue in a for loop still runs the step (i++); it just skips back to the condition check.

The genuine infinite loop

Sometimes you actually want a loop that never stops on its own — a daemon that listens forever, an event loop, a game loop. The C idiom is:

for (;;) {
    // wait for an event
    // handle it
    // repeat forever
}

for (;;) is "no initialiser, no condition, no step" — a loop with nothing to stop it. while (1) works equally well. The exit happens via break, or a return, or a signal from the OS, or the end of the universe. Every long-running server in the world has one of these somewhere near its main function.

Pitfalls and traps

Why this matters for AI

Almost every line of an AI training run is, ultimately, a loop. The outermost loop walks epochs (passes through your dataset). Inside that, batches. Inside that, layers. Inside that, individual matrix elements — billions of them per batch.

The interesting part is that, by the time you get down to the innermost loop, the whole thing has been reorganised: the explicit Python for i in range(...) has been replaced with a single matrix operation, which is itself a heavily-optimised C++/CUDA kernel that uses SIMD and tiling and prefetching to keep the chef perfectly fed. The loop is still there. It's just been compressed into a single very hot, very specialised kernel. Vectorising a loop — turning for i in range into vector_op(...) — is one of the most common, highest-leverage optimisations in numerical code.

And the train_loop at the very top is, often, just while not converged: ... — a literal infinite loop interrupted by a stopping criterion. The whole AI revolution rests on a few well-tuned fors.

The loop is the engine of every long-running computation in history. Three keywords in C; the entire fabric of modern computing.

Try it yourself

What's next

You can now write functions that use variables, decide things, and repeat. You're a real programmer in a small but real way. Next week we descend one floor — into the part of C that other languages chose to abstract away because it was, quite genuinely, terrifying.

Week 17 is The Scary Part — pointers and memory addresses. Where C reveals what's actually been happening this whole time, and where every other language's "kindness" is, in truth, an enormous lie.

Photo credit

Photo free under the Unsplash license. Gears · Laura Ockel.