The C Path — learn C, visually

🌱 C Basics

break, continue & goto: bending the flow

⏱ 13 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

When your music app finds the song you searched for, it stops — it doesn't keep scanning the other 10,000 tracks. break and continue are the stop and skip buttons of loops, and you'll also discover why the "forbidden" goto still appears thousands of times, on purpose, in the code that runs Linux.

Loops usually end when their condition says so — but real code often needs an emergency exit ("found it, stop searching!") or a skip button ("not interested in this one, next!"). C provides three flow-bending keywords: break, continue, and the notorious goto.

break: eject from the loop

break immediately terminates the innermost enclosing loop (or switch, as you saw last lesson) and resumes after its closing brace:

This spot has an interactive flow widget — open the interactive lesson to play with it.

firstdiv.c — break when found
#include <stdio.h>

int main(void) {
    int n = 91;   /* is it prime? find a divisor */
    int divisor = 0;

    for (int d = 2; d * d <= n; d++) {
        if (n % d == 0) {
            divisor = d;
            break;            /* found one — stop searching */
        }
    }

    if (divisor)
        printf("%d = %d x %d\n", n, divisor, n / divisor);
    else
        printf("%d is prime\n", n);
    return 0;
}
terminal
$ gcc firstdiv.c -o firstdiv && ./firstdiv
91 = 7 x 13
# without break we'd keep testing 8, 9, ... for nothing

🧠 Checkpoint: What does break inside the inner one of two nested loops do?

  • Exits both loops
  • Exits only the inner loop; the outer continues
  • Skips one iteration of the inner loop
  • Ends the program
Show answer

Exits only the inner loop; the outer continues — break always escapes exactly one level — the innermost loop or switch it sits in. The outer loop never notices. Multi-level escapes need a flag, a return, or a forward goto.

continue: skip to the next lap

continue abandons the current iteration only: it jumps straight to the next condition check (in a for, the step still runs first — the loop stays healthy). The loop itself keeps going:

This spot has an interactive flow widget — open the interactive lesson to play with it.

odds.c — continue to filter
#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 8; i++) {
        if (i % 2 == 0) {
            continue;         /* even? not interested */
        }
        printf("%d is odd\n", i);
    }
    return 0;
}
terminal
$ gcc odds.c -o odds && ./odds
1 is odd
3 is odd
5 is odd
7 is odd
⚠️

break only escapes ONE level. In nested loops, a break in the inner loop returns you to the outer loop, which happily continues. There is no break 2; in C (sorry, PHP folks). To exit several levels you can set a flag and test it in each condition, move the loops into a function and return… or use the one legitimate goto below.

🧠 Checkpoint: In a for loop, continue jumps to…

  • the first line of the function
  • the condition, skipping the step
  • the step expression, then the condition
  • right after the loop
Show answer

the step expression, then the condition — In for loops the step is sacred: continue runs it before re-testing. (In a while loop, continue jumps straight to the condition — which is why converting a for to a while with continue inside can create an infinite loop!)

goto: the keyword with a reputation

goto jumps to a label — a name followed by a colon, marking a spot in the same function. Dijkstra's famous 1968 letter "Go To Statement Considered Harmful" made it programming's boogeyman, and yes: spaghetti of upward, criss-crossing gotos is unmaintainable.

But modern C has two respectable uses, both jumping strictly forward and downward:

cleanup.c — the respectable goto
#include <stdio.h>
#include <stdlib.h>

int process(void) {
    int status = -1;
    FILE *log = NULL;
    char *buf = NULL;

    log = fopen("run.log", "w");
    if (log == NULL) goto out;          /* nothing to undo yet  */

    buf = malloc(4096);
    if (buf == NULL) goto close_log;    /* must undo the fopen  */

    /* ... real work with log and buf ... */
    status = 0;                         /* success!             */

    free(buf);                          /* fall into cleanup    */
close_log:
    fclose(log);
out:
    return status;
}

Why this is good design: every failure path releases exactly the resources acquired so far, in reverse order, with a single copy of the cleanup code. Without goto you'd repeat the frees in every error branch — and one day forget one (hello, memory leak). Don't worry about the malloc/NULL details yet; Part 2 covers them properly. The shape is what matters.

💡

The house rules for goto: jump forward only, jump downward only, target cleanup/exit labels only. Within those fences it's not spaghetti — it's C's substitute for exceptions.

🧠 Checkpoint: Why is the goto-cleanup pattern considered good C style?

  • goto is faster than function calls
  • It lets loops run backwards
  • Every error path funnels through one copy of the cleanup code, released in reverse order
  • It replaces the need for return
Show answer

Every error path funnels through one copy of the cleanup code, released in reverse order — One exit ramp, one copy of each fclose/free, impossible to "forget a free in the third error branch". Forward-only jumps to cleanup labels are idiomatic C — the Linux kernel does it thousands of times.

This spot has an interactive editor widget — open the interactive lesson to play with it.

Our loops are getting long enough to want names of their own — time to package code into functions.

▶ Practice this lesson interactively (with live gcc)