break, continue and goto

break leaves a loop early and continue skips to the next pass. C also has goto, which jumps to a label — usually best avoided, but with one widely accepted use: jumping to shared clean-up code when something fails.

break

break exits the innermost loop (or switch) immediately — perfect for searching:

C
#include <stdio.h>

int main(void) {
    int ids[] = { 104, 220, 317, 431, 562 };
    int wanted = 317;
    int found_at = -1;
    for (int i = 0; i < 5; i++) {
        if (ids[i] == wanted) {
            found_at = i;
            break;
        }
    }
    if (found_at >= 0) {
        printf("Found %d at position %d\n", wanted, found_at);
    } else {
        printf("%d not found\n", wanted);
    }
    return 0;
}
Output
Found 317 at position 2

continue

continue skips the rest of the current pass. In a for loop, the update part (i++) still runs:

C
#include <stdio.h>

int main(void) {
    int readings[] = { 21, -999, 23, 22, -999, 25 };   // -999 = sensor error
    int sum = 0, valid = 0;
    for (int i = 0; i < 6; i++) {
        if (readings[i] == -999) {
            continue;
        }
        sum += readings[i];
        valid++;
    }
    printf("Average of %d valid readings: %.1f\n", valid, (double) sum / valid);
    return 0;
}
Output
Average of 4 valid readings: 22.8

"Skip the bad cases first" keeps the main work un-nested — the same idea as early returns in functions.

Leaving nested loops

break only exits one level. To stop two nested loops at once, use a flag — or, often cleaner, put the loops in a function and return:

C
#include <stdio.h>
#include <stdbool.h>

bool find_pair(const int *values, int n, int target, int *a, int *b) {
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (values[i] + values[j] == target) {
                *a = values[i];
                *b = values[j];
                return true;
            }
        }
    }
    return false;
}

int main(void) {
    int values[] = { 8, 3, 11, 5, 14 };
    int a, b;
    if (find_pair(values, 5, 16, &a, &b)) {
        printf("%d + %d = 16\n", a, b);
    }
    return 0;
}
Output
11 + 5 = 16

goto

goto label; jumps to label: elsewhere in the same function. Used freely it creates tangled "spaghetti code", and most of the time loops, functions and return are clearer.

The accepted exception is error handling with clean-up. When a function acquires several resources (memory, files) and any step can fail, jumping forward to one clean-up section avoids repeating the same release code in every error branch. You'll see this pattern throughout real C code, including the Linux kernel:

C
#include <stdio.h>
#include <stdlib.h>

int process(size_t n) {
    int status = -1;
    double *data = NULL;
    double *copy = NULL;

    data = malloc(n * sizeof *data);
    if (data == NULL) goto cleanup;

    copy = malloc(n * sizeof *copy);
    if (copy == NULL) goto cleanup;

    for (size_t i = 0; i < n; i++) {
        data[i] = i * 1.5;
        copy[i] = data[i] * 2;
    }
    printf("last copied value: %.1f\n", copy[n - 1]);
    status = 0;

cleanup:
    free(copy);
    free(data);
    return status;
}

int main(void) {
    return process(4);
}
Output
last copied value: 9.0

malloc and free come in lesson 36. The rules for this pattern: only jump forward, only to clean-up code at the end of the function, and initialise every resource to "nothing" (NULL) first so the clean-up is safe whichever step failed.

Try it yourself

Print the numbers from 1 to 30, skipping multiples of 3 and stopping completely at the first number over 20 that's divisible by 7.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
1 2 4 5 7 8 10 11 13 14 16 17 19 20