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:
#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; }
Found 317 at position 2
continue
continue skips the rest of the current pass. In a for loop, the update part (i++) still runs:
#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; }
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:
#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; }
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:
#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); }
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.
1 2 4 5 7 8 10 11 13 14 16 17 19 20