Control Flow in C
Learn if/else, switch, for/while/do-while loops, break, continue, and the goto statement in C.
if / else
The if/else statement is the most fundamental control flow construct. It lets your program make decisions: execute one block of code when a condition is true, and a different block when it is false. Chaining else if handles multiple mutually exclusive cases.
#include <stdio.h>
int main(void) {
int score = 75;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else if (score >= 60) {
printf("Grade: D\n");
} else {
printf("Grade: F\n");
}
/* Single-statement branches don't require braces, but always use them anyway */
int x = 10;
if (x > 0)
printf("positive\n"); /* valid but error-prone */
/* Always use braces to avoid the dangling else problem */
if (x > 0) {
if (x > 100) {
printf("large\n");
}
} else {
printf("non-positive\n"); /* belongs to outer if, not inner */
}
return 0;
}
switch
switch tests a single integer expression against a set of constant values. It is cleaner than a long if/else if chain when you have many distinct cases, and the compiler can often optimize it into a jump table for O(1) dispatch.
#include <stdio.h>
int main(void) {
char op = '+';
int a = 10, b = 3;
switch (op) {
case '+':
printf("%d\n", a + b);
break; /* without break, execution falls through to the next case */
case '-':
printf("%d\n", a - b);
break;
case '*':
printf("%d\n", a * b);
break;
case '/':
if (b != 0) printf("%d\n", a / b);
else printf("Division by zero\n");
break;
default:
printf("Unknown operator: %c\n", op);
break;
}
return 0;
}
Intentional Fall-Through
Omitting break intentionally lets multiple cases share the same handler. This is useful when several values should produce the same result — document it with a comment so readers know it’s deliberate.
int day = 3; /* Wednesday */
switch (day) {
case 1: /* Monday */
case 2: /* Tuesday */
case 3: /* Wednesday */
case 4: /* Thursday */
case 5: /* Friday */
printf("Weekday\n");
break;
case 6: /* Saturday */
case 7: /* Sunday */
printf("Weekend\n");
break;
default:
printf("Invalid day\n");
}
switch only works with integer types (int, char, enum). It cannot test strings, floats, or ranges.
for Loop
The for loop is ideal when you know the number of iterations in advance. Its three-part header — initialize, test, update — keeps the loop control logic in one place, making the code easy to scan.
#include <stdio.h>
int main(void) {
/* Basic for loop */
for (int i = 0; i < 5; i++) {
printf("%d ", i); /* 0 1 2 3 4 */
}
printf("\n");
/* Count down */
for (int i = 10; i > 0; i--) {
printf("%d ", i);
}
printf("\n");
/* Multiple variables using the comma operator */
for (int i = 0, j = 10; i < j; i++, j--) {
printf("(%d,%d) ", i, j);
}
printf("\n");
/* Infinite loop — rely on break to exit */
int n = 1;
for (;;) {
if (n > 8) break;
printf("%d ", n);
n *= 2; /* 1 2 4 8 */
}
printf("\n");
/* Nested loops: goto is the cleanest way to break out of both at once */
int found_i = -1, found_j = -1;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i * j == 6) {
found_i = i;
found_j = j;
goto done; /* one legitimate use of goto */
}
}
}
done:
printf("Found at i=%d, j=%d\n", found_i, found_j); /* i=2, j=3 */
return 0;
}
while Loop
The while loop tests its condition before each iteration. Use it when you don’t know the number of iterations in advance — the body may execute zero times if the condition is false at the start.
#include <stdio.h>
int main(void) {
/* while: test before first iteration — may run 0 times */
int n = 1;
while (n < 100) {
printf("%d ", n);
n *= 2; /* 1 2 4 8 16 32 64 */
}
printf("\n");
/* Classic use: read until EOF */
int ch;
printf("Enter characters (Ctrl+D to stop):\n");
while ((ch = getchar()) != EOF) {
putchar(ch);
}
return 0;
}
do-while Loop
do-while always executes the body at least once because the condition is tested at the end, not the beginning. This is the right choice for menus and input validation where you must execute the body before you have a value to test.
#include <stdio.h>
int main(void) {
int input;
/* Menu loop — show the menu at least once before checking the choice */
do {
printf("Menu:\n");
printf(" 1. Option A\n");
printf(" 2. Option B\n");
printf(" 0. Quit\n");
printf("Choice: ");
scanf("%d", &input);
} while (input != 0);
printf("Goodbye\n");
return 0;
}
Another common use — computing digit count where you always need at least one iteration:
unsigned int n = 12345;
int digit_count = 0;
do {
digit_count++;
n /= 10;
} while (n > 0);
/* digit_count == 5, and correctly handles n=0 (gives 1 digit) */
break and continue
break exits the nearest enclosing loop or switch immediately. continue skips the rest of the current loop body and jumps to the next iteration’s condition check. Both make loops easier to read when used judiciously.
#include <stdio.h>
int main(void) {
/* break: exit the loop early when a condition is met */
for (int i = 0; i < 10; i++) {
if (i == 5) break;
printf("%d ", i); /* 0 1 2 3 4 */
}
printf("\n");
/* continue: skip even numbers, only print odd ones */
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue;
printf("%d ", i); /* 1 3 5 7 9 */
}
printf("\n");
/* break only exits the innermost loop — not all enclosing loops */
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) break; /* only exits j loop, i loop continues */
printf("(%d,%d) ", i, j);
}
}
/* prints: (0,0) (1,0) (2,0) */
printf("\n");
return 0;
}
goto — and Why to Avoid It
goto transfers control unconditionally to a labeled statement in the same function. Most uses make code harder to read and debug, but one pattern is widely accepted in C: single exit point with cleanup. When a function acquires multiple resources and any step can fail, goto cleanup avoids deeply nested if blocks or duplicated cleanup code.
#include <stdio.h>
#include <stdlib.h>
int process_data(const char *filename) {
FILE *fp = NULL;
char *buffer = NULL;
int result = -1;
fp = fopen(filename, "r");
if (!fp) {
fprintf(stderr, "Cannot open file\n");
goto cleanup; /* jump directly to cleanup, skipping remaining setup */
}
buffer = malloc(4096);
if (!buffer) {
fprintf(stderr, "Out of memory\n");
goto cleanup;
}
/* ... actual processing ... */
result = 0; /* success */
cleanup:
free(buffer); /* safe: free(NULL) is a no-op */
if (fp) fclose(fp);
return result;
}
Without goto, you’d need nested if blocks or duplicate cleanup code at every exit point. The Linux kernel uses this pattern extensively.
Never use goto to jump forward over declarations, into nested blocks, or across function boundaries. Only use it to jump forward to cleanup code at the end of the current function.
Loop Comparison
| Loop | Use when… |
|---|---|
for | Number of iterations is known ahead of time |
while | Loop while a condition holds, may run 0 times |
do-while | Must run at least once (menus, input validation) |
When all three can work, prefer for for iteration over ranges and while for event-driven loops. Avoid do-while unless you specifically need the “at least once” guarantee — it’s the least common and most often misread.