Skip to main content

Control Flow: if, switch, loops, and the for idiom

What This Concept Is​

C's control flow is minimal and mostly familiar, but the idioms matter because they are what other C programmers read for.

The core pieces:

  • if (expr) stmt / if (expr) stmt else stmt - any non-zero expr is "true"
  • switch (expr) { case C1: ... break; default: ... } - integer dispatch with implicit fall-through
  • while (expr) stmt - test then body
  • do stmt while (expr); - body then test (runs at least once)
  • for (init; cond; step) stmt - equivalent to init; while (cond) { stmt; step; }
  • break exits the innermost switch or loop; continue jumps to the step of the innermost loop
  • goto label; jumps within a function; used in well-placed error-handling chains

The for idiom in C is structured loop plumbing: initialization, termination test, and increment all in one place, so the shape of the loop is visible at the top.

Why It Matters Here​

The rest of the module and the next module lean on two habits:

  1. Write loops with a clear termination argument.
  2. Do not rely on incidental fall-through in switch; document any intentional one.

Off-by-one and accidental fall-through are two of the most common C bugs. Control-flow discipline is where you stop them.

Concrete Example​

The canonical for loop over an array:

int arr[N];
for (size_t i = 0; i < N; i++) {
arr[i] = (int)i;
}

switch with an explicit fall-through:

switch (c) {
case ' ':
case '\t':
case '\n':
handle_whitespace();
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
handle_digit(c);
break;
default:
handle_other(c);
break;
}

Reasonable goto for error handling:

int f(void) {
FILE *fp = fopen("x", "r");
if (!fp) goto fail_fp;
char *buf = malloc(N);
if (!buf) goto fail_buf;
/* ... real work ... */
free(buf);
fclose(fp);
return 0;
fail_buf:
fclose(fp);
fail_fp:
return -1;
}

Common Confusion / Misconception​

"switch is a cleaner if/else." It is not. Every case without a terminating break falls into the next one, which is both a feature and a constant source of bugs. Always close each non-fall-through case with break or return.

"goto is always wrong." In C, a forward-only goto to a single cleanup block is idiomatic. What is wrong is backward goto used as a substitute for a loop.

How To Use It​

  1. Default to for (size_t i = 0; i < n; i++) for counted iteration. Use while when the termination test is the focus, not the counter.
  2. Every case either ends with break, return, goto, or a deliberate comment /* fall through */.
  3. Declare the loop variable inside the for when using C99 or later.
  4. Keep the body of each control-flow construct indented; never omit the braces on multi-line bodies.
  5. Use goto only for forward jumps to error-cleanup, and only when the alternative is deeper nesting.

Check Yourself​

  1. In for (i = 0; i < n; i++) sum += a[i];, name three ways you could get an off-by-one.
  2. Why does switch require break?
  3. When is do/while the right choice instead of while?

Mini Drill or Application​

Write, by hand, without looking them up:

  1. A while loop that reads characters from stdin until EOF using getchar and counts newlines.
  2. A for loop that sums arr[0..n-1] into long long sum and avoids signed overflow.
  3. A switch on a char that classifies vowels, consonants, digits, whitespace, and other.
  4. An error-handling pattern using a single goto cleanup; target that releases two resources.

Read This Only If Stuck​