C — Control Flow
Control flow determines the order in which statements execute. C provides conditional branching (if, switch), three loop constructs (for, while, do-while), and jump statements (break, continue, goto, return). Mastering these constructs is essential for writing efficient, correct C programs.
Unlike some languages, C treats any non-zero value as true and zero as false. This applies to if conditions, loop conditions, and logical operators. There is no separate boolean type in C89 — C99 introduced _Bool via <stdbool.h>.
This page covers conditionals, loops, jump statements, common patterns, and comprehensive examples.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | int score = 85; |
| 5 | |
| 6 | if (score >= 90) { |
| 7 | printf("Grade: A\n"); |
| 8 | } else if (score >= 80) { |
| 9 | printf("Grade: B\n"); |
| 10 | } else if (score >= 70) { |
| 11 | printf("Grade: C\n"); |
| 12 | } else { |
| 13 | printf("Grade: F\n"); |
| 14 | } |
| 15 | |
| 16 | // Any non-zero value is true |
| 17 | int flag = 42; |
| 18 | if (flag) { |
| 19 | printf("flag is truthy\n"); |
| 20 | } |
| 21 | |
| 22 | return 0; |
| 23 | } |
The if statement evaluates a condition and executes the body if it's non-zero. The else clause executes when the condition is zero. You can chain conditions with else if.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | int x = 15; |
| 5 | |
| 6 | if (x > 0) { |
| 7 | printf("Positive\n"); |
| 8 | } else if (x < 0) { |
| 9 | printf("Negative\n"); |
| 10 | } else { |
| 11 | printf("Zero\n"); |
| 12 | } |
| 13 | |
| 14 | // Dangling else problem |
| 15 | // Without braces, else binds to the NEAREST if |
| 16 | int a = 1, b = 2; |
| 17 | |
| 18 | // WRONG — else binds to inner if, not outer |
| 19 | if (a) |
| 20 | if (b) |
| 21 | printf("Both true\n"); |
| 22 | else // This else matches the INNER if |
| 23 | printf("This is confusing\n"); |
| 24 | |
| 25 | // CORRECT — always use braces |
| 26 | if (a) { |
| 27 | if (b) { |
| 28 | printf("Both true (correct)\n"); |
| 29 | } else { |
| 30 | printf("b is false (correct)\n"); |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | // Nested if for range checking |
| 35 | int age = 25; |
| 36 | if (age >= 0) { |
| 37 | if (age < 13) { |
| 38 | printf("Child\n"); |
| 39 | } else if (age < 18) { |
| 40 | printf("Teenager\n"); |
| 41 | } else if (age < 65) { |
| 42 | printf("Adult\n"); |
| 43 | } else { |
| 44 | printf("Senior\n"); |
| 45 | } |
| 46 | } else { |
| 47 | printf("Invalid age\n"); |
| 48 | } |
| 49 | |
| 50 | return 0; |
| 51 | } |
info
The switch statement selects among multiple cases based on an integer expression. It's more readable than long else if chains when comparing the same variable against many values.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | int day = 3; |
| 5 | |
| 6 | switch (day) { |
| 7 | case 1: |
| 8 | printf("Monday\n"); |
| 9 | break; // Without break, execution "falls through" |
| 10 | case 2: |
| 11 | printf("Tuesday\n"); |
| 12 | break; |
| 13 | case 3: |
| 14 | printf("Wednesday\n"); |
| 15 | break; |
| 16 | case 4: |
| 17 | case 5: |
| 18 | printf("Thursday or Friday\n"); // Intentional fall-through |
| 19 | break; |
| 20 | case 6: |
| 21 | case 7: |
| 22 | printf("Weekend\n"); |
| 23 | break; |
| 24 | default: |
| 25 | printf("Invalid day\n"); |
| 26 | break; |
| 27 | } |
| 28 | |
| 29 | // switch requires integer expressions (int, char, enum) |
| 30 | // You CANNOT use: float, double, string, or complex types |
| 31 | |
| 32 | // Multiple case labels for the same code |
| 33 | char grade = 'B'; |
| 34 | switch (grade) { |
| 35 | case 'A': case 'a': |
| 36 | printf("Excellent\n"); |
| 37 | break; |
| 38 | case 'B': case 'b': |
| 39 | printf("Good\n"); |
| 40 | break; |
| 41 | case 'F': case 'f': |
| 42 | printf("Fail\n"); |
| 43 | break; |
| 44 | default: |
| 45 | printf("Unknown grade\n"); |
| 46 | break; |
| 47 | } |
| 48 | |
| 49 | return 0; |
| 50 | } |
| Feature | switch | if-else |
|---|---|---|
| Best for | Comparing one variable against many constants | Complex conditions, ranges |
| Condition type | Integer expression only | Any expression |
| Fall-through | Yes (must use break) | No |
| Performance | Jump table (fast for many cases) | Sequential evaluation |
| Duplicate cases | Not allowed | Allowed (last wins) |
warning
The for loop is the most versatile loop construct in C. It combines initialization, condition, and increment into a single line. All three parts are optional.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | // Standard counting loop |
| 5 | for (int i = 0; i < 10; i++) { |
| 6 | printf("%d ", i); |
| 7 | } |
| 8 | printf("\n"); |
| 9 | |
| 10 | // Counting backwards |
| 11 | for (int i = 10; i > 0; i--) { |
| 12 | printf("%d ", i); |
| 13 | } |
| 14 | printf("\n"); |
| 15 | |
| 16 | // Skip by 2 |
| 17 | for (int i = 0; i < 20; i += 2) { |
| 18 | printf("%d ", i); |
| 19 | } |
| 20 | printf("\n"); |
| 21 | |
| 22 | // Infinite loop (all parts optional) |
| 23 | int count = 0; |
| 24 | for (;;) { |
| 25 | if (count >= 5) break; |
| 26 | printf("count: %d\n", count++); |
| 27 | } |
| 28 | |
| 29 | // Multiple loop variables |
| 30 | for (int i = 0, j = 10; i < j; i++, j--) { |
| 31 | printf("i=%d, j=%d\n", i, j); |
| 32 | } |
| 33 | |
| 34 | // Nested for loops — multiplication table |
| 35 | for (int i = 1; i <= 5; i++) { |
| 36 | for (int j = 1; j <= 5; j++) { |
| 37 | printf("%4d", i * j); |
| 38 | } |
| 39 | printf("\n"); |
| 40 | } |
| 41 | |
| 42 | return 0; |
| 43 | } |
The while loop evaluates the condition before each iteration. If the condition is false on the first check, the body never executes.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | // Basic while loop |
| 5 | int i = 0; |
| 6 | while (i < 5) { |
| 7 | printf("%d ", i); |
| 8 | i++; |
| 9 | } |
| 10 | printf("\n"); |
| 11 | |
| 12 | // Reading input until sentinel value |
| 13 | int sum = 0, val; |
| 14 | printf("Enter numbers (0 to stop): "); |
| 15 | while (scanf("%d", &val) == 1 && val != 0) { |
| 16 | sum += val; |
| 17 | } |
| 18 | printf("Sum: %d\n", sum); |
| 19 | |
| 20 | // Processing a linked list (common pattern) |
| 21 | // while (current != NULL) { |
| 22 | // process(current); |
| 23 | // current = current->next; |
| 24 | // } |
| 25 | |
| 26 | // Factorial using while |
| 27 | int n = 5, fact = 1; |
| 28 | while (n > 1) { |
| 29 | fact *= n--; |
| 30 | } |
| 31 | printf("5! = %d\n", fact); // 120 |
| 32 | |
| 33 | return 0; |
| 34 | } |
The do-while loop evaluates the condition aftereach iteration, guaranteeing the body executes at least once. It's ideal for input validation and menu systems.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | // Input validation — must run at least once |
| 5 | int age; |
| 6 | do { |
| 7 | printf("Enter your age (1-120): "); |
| 8 | scanf("%d", &age); |
| 9 | } while (age < 1 || age > 120); |
| 10 | |
| 11 | printf("Age: %d\n", age); |
| 12 | |
| 13 | // Menu system |
| 14 | int choice; |
| 15 | do { |
| 16 | printf("\n--- Menu ---\n"); |
| 17 | printf("1. Play\n"); |
| 18 | printf("2. Settings\n"); |
| 19 | printf("3. Quit\n"); |
| 20 | printf("Choice: "); |
| 21 | scanf("%d", &choice); |
| 22 | |
| 23 | switch (choice) { |
| 24 | case 1: printf("Starting game...\n"); break; |
| 25 | case 2: printf("Opening settings...\n"); break; |
| 26 | case 3: printf("Goodbye!\n"); break; |
| 27 | default: printf("Invalid choice\n"); break; |
| 28 | } |
| 29 | } while (choice != 3); |
| 30 | |
| 31 | // Number guessing game |
| 32 | int secret = 42, guess, attempts = 0; |
| 33 | do { |
| 34 | printf("Guess (1-100): "); |
| 35 | scanf("%d", &guess); |
| 36 | attempts++; |
| 37 | if (guess < secret) printf("Too low!\n"); |
| 38 | else if (guess > secret) printf("Too high!\n"); |
| 39 | else printf("Correct in %d attempts!\n", attempts); |
| 40 | } while (guess != secret); |
| 41 | |
| 42 | return 0; |
| 43 | } |
Nesting loops is essential for multi-dimensional iteration — matrices, grids, and combinatorial algorithms. Each inner loop runs to completion for every iteration of the outer loop.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | #define ROWS 3 |
| 4 | #define COLS 4 |
| 5 | |
| 6 | int main(void) { |
| 7 | // Matrix initialization and printing |
| 8 | int matrix[ROWS][COLS] = { |
| 9 | {1, 2, 3, 4}, |
| 10 | {5, 6, 7, 8}, |
| 11 | {9, 10, 11, 12} |
| 12 | }; |
| 13 | |
| 14 | // Print matrix |
| 15 | for (int i = 0; i < ROWS; i++) { |
| 16 | for (int j = 0; j < COLS; j++) { |
| 17 | printf("%4d", matrix[i][j]); |
| 18 | } |
| 19 | printf("\n"); |
| 20 | } |
| 21 | |
| 22 | // Find maximum value |
| 23 | int max = matrix[0][0]; |
| 24 | for (int i = 0; i < ROWS; i++) { |
| 25 | for (int j = 0; j < COLS; j++) { |
| 26 | if (matrix[i][j] > max) { |
| 27 | max = matrix[i][j]; |
| 28 | } |
| 29 | } |
| 30 | } |
| 31 | printf("Max: %d\n", max); |
| 32 | |
| 33 | // Transpose matrix |
| 34 | int transposed[COLS][ROWS]; |
| 35 | for (int i = 0; i < ROWS; i++) { |
| 36 | for (int j = 0; j < COLS; j++) { |
| 37 | transposed[j][i] = matrix[i][j]; |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | return 0; |
| 42 | } |
break exits the innermost loop or switch immediately. continue skips to the next iteration of the innermost loop. Both only affect the innermost enclosing loop.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | // break — exit loop early |
| 5 | for (int i = 0; i < 100; i++) { |
| 6 | if (i == 10) { |
| 7 | printf("Found 10, stopping\n"); |
| 8 | break; // exits the for loop |
| 9 | } |
| 10 | } |
| 11 | |
| 12 | // continue — skip to next iteration |
| 13 | for (int i = 0; i < 10; i++) { |
| 14 | if (i % 2 == 0) { |
| 15 | continue; // skip even numbers |
| 16 | } |
| 17 | printf("%d ", i); // prints: 1 3 5 7 9 |
| 18 | } |
| 19 | printf("\n"); |
| 20 | |
| 21 | // break in nested loops — only exits inner loop |
| 22 | for (int i = 0; i < 5; i++) { |
| 23 | for (int j = 0; j < 5; j++) { |
| 24 | if (j == 3) break; // exits inner loop only |
| 25 | printf("(%d,%d) ", i, j); |
| 26 | } |
| 27 | printf("\n"); |
| 28 | } |
| 29 | |
| 30 | // Pattern: find first prime after 100 |
| 31 | for (int n = 101; ; n++) { |
| 32 | int is_prime = 1; |
| 33 | for (int i = 2; i * i <= n; i++) { |
| 34 | if (n % i == 0) { |
| 35 | is_prime = 0; |
| 36 | break; |
| 37 | } |
| 38 | } |
| 39 | if (is_prime) { |
| 40 | printf("First prime after 100: %d\n", n); |
| 41 | break; |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | return 0; |
| 46 | } |
The goto statement performs an unconditional jump to a labeled statement. It is generally discouraged, but has one legitimate use: centralized cleanup in functions with multiple resources to free.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | // goto for cleanup — the one legitimate pattern |
| 5 | int process_data(const char *filename) { |
| 6 | FILE *fp = NULL; |
| 7 | char *buffer = NULL; |
| 8 | int result = -1; |
| 9 | |
| 10 | fp = fopen(filename, "r"); |
| 11 | if (fp == NULL) goto cleanup; |
| 12 | |
| 13 | buffer = malloc(1024); |
| 14 | if (buffer == NULL) goto cleanup; |
| 15 | |
| 16 | // ... process data ... |
| 17 | if (fgets(buffer, 1024, fp) == NULL) goto cleanup; |
| 18 | |
| 19 | printf("Data: %s\n", buffer); |
| 20 | result = 0; // success |
| 21 | |
| 22 | cleanup: |
| 23 | if (buffer) free(buffer); |
| 24 | if (fp) fclose(fp); |
| 25 | return result; |
| 26 | } |
| 27 | |
| 28 | // Equivalent without goto — more verbose |
| 29 | int process_data_nogoto(const char *filename) { |
| 30 | FILE *fp = fopen(filename, "r"); |
| 31 | if (fp == NULL) return -1; |
| 32 | |
| 33 | char *buffer = malloc(1024); |
| 34 | if (buffer == NULL) { |
| 35 | fclose(fp); |
| 36 | return -1; |
| 37 | } |
| 38 | |
| 39 | if (fgets(buffer, 1024, fp) == NULL) { |
| 40 | free(buffer); |
| 41 | fclose(fp); |
| 42 | return -1; |
| 43 | } |
| 44 | |
| 45 | printf("Data: %s\n", buffer); |
| 46 | free(buffer); |
| 47 | fclose(fp); |
| 48 | return 0; |
| 49 | } |
| 50 | |
| 51 | int main(void) { |
| 52 | // Avoid goto for general flow control |
| 53 | // goto is only useful for multi-resource cleanup |
| 54 | printf("See process_data() for goto cleanup pattern\n"); |
| 55 | return 0; |
| 56 | } |
note
Infinite loops are common in servers, event loops, and embedded systems. C provides several syntactically valid ways to write them, each with different readability and portability characteristics.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | // Preferred: for(;;) — most idiomatic in C |
| 5 | int count = 0; |
| 6 | for (;;) { |
| 7 | if (count >= 3) break; |
| 8 | printf("for(;;): %d\n", count++); |
| 9 | } |
| 10 | |
| 11 | // Also common: while(1) — clear intent |
| 12 | count = 0; |
| 13 | while (1) { |
| 14 | if (count >= 3) break; |
| 15 | printf("while(1): %d\n", count++); |
| 16 | } |
| 17 | |
| 18 | // while(1) — equivalent |
| 19 | count = 0; |
| 20 | while (1) { |
| 21 | if (count >= 3) break; |
| 22 | printf("while(1): %d\n", count++); |
| 23 | } |
| 24 | |
| 25 | // Practical: server event loop pattern |
| 26 | // while (1) { |
| 27 | // Client *client = accept(server_fd, ...); |
| 28 | // handle_client(client); |
| 29 | // } |
| 30 | |
| 31 | // Practical: embedded main loop |
| 32 | // for (;;) { |
| 33 | // check_sensors(); |
| 34 | // update_display(); |
| 35 | // __asm__("wfi"); // wait for interrupt |
| 36 | // } |
| 37 | |
| 38 | return 0; |
| 39 | } |
Control flow patterns recur frequently in C programs. Mastering these patterns helps you write idiomatic, efficient code.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Pattern 1: Sentinel-controlled loop |
| 4 | void read_names(void) { |
| 5 | char name[64]; |
| 6 | printf("Enter names (type 'quit' to stop):\n"); |
| 7 | while (scanf("%63s", name) == 1) { |
| 8 | if (name[0] == 'q' && name[1] == 'u') break; |
| 9 | printf(" Hello, %s!\n", name); |
| 10 | } |
| 11 | } |
| 12 | |
| 13 | // Pattern 2: Flag-controlled loop |
| 14 | void find_first_negative(int arr[], int n) { |
| 15 | int found = 0; |
| 16 | for (int i = 0; i < n && !found; i++) { |
| 17 | if (arr[i] < 0) { |
| 18 | printf("First negative at index %d\n", i); |
| 19 | found = 1; |
| 20 | } |
| 21 | } |
| 22 | if (!found) printf("No negatives found\n"); |
| 23 | } |
| 24 | |
| 25 | // Pattern 3: Nested iteration with early exit |
| 26 | int has_common_element(int a[], int na, int b[], int nb) { |
| 27 | for (int i = 0; i < na; i++) { |
| 28 | for (int j = 0; j < nb; j++) { |
| 29 | if (a[i] == b[j]) return 1; // found |
| 30 | } |
| 31 | } |
| 32 | return 0; // not found |
| 33 | } |
| 34 | |
| 35 | // Pattern 4: Switch-based state machine |
| 36 | enum State { IDLE, RUNNING, PAUSED, STOPPED }; |
| 37 | |
| 38 | int main(void) { |
| 39 | int data[] = {3, -1, 4, -2, 5}; |
| 40 | int set1[] = {1, 3, 5}; |
| 41 | int set2[] = {2, 4, 6}; |
| 42 | |
| 43 | read_names(); |
| 44 | find_first_negative(data, 5); |
| 45 | printf("Common: %s\n", |
| 46 | has_common_element(set1, 3, set2, 3) ? "yes" : "no"); |
| 47 | |
| 48 | return 0; |
| 49 | } |
These examples combine multiple control flow constructs to solve practical problems.
| 1 | #include <stdio.h> |
| 2 | #include <math.h> |
| 3 | |
| 4 | // Prime number checker |
| 5 | int is_prime(int n) { |
| 6 | if (n < 2) return 0; |
| 7 | if (n == 2) return 1; |
| 8 | if (n % 2 == 0) return 0; |
| 9 | for (int i = 3; i <= (int)sqrt((double)n); i += 2) { |
| 10 | if (n % i == 0) return 0; |
| 11 | } |
| 12 | return 1; |
| 13 | } |
| 14 | |
| 15 | // Print number pyramid |
| 16 | void print_pyramid(int rows) { |
| 17 | for (int i = 1; i <= rows; i++) { |
| 18 | // Print leading spaces |
| 19 | for (int j = 0; j < rows - i; j++) { |
| 20 | printf(" "); |
| 21 | } |
| 22 | // Print ascending numbers |
| 23 | for (int j = 1; j <= i; j++) { |
| 24 | printf("%d", j); |
| 25 | } |
| 26 | // Print descending numbers |
| 27 | for (int j = i - 1; j >= 1; j--) { |
| 28 | printf("%d", j); |
| 29 | } |
| 30 | printf("\n"); |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | int main(void) { |
| 35 | // Find primes up to 50 |
| 36 | printf("Primes up to 50:\n"); |
| 37 | for (int i = 2; i <= 50; i++) { |
| 38 | if (is_prime(i)) { |
| 39 | printf("%d ", i); |
| 40 | } |
| 41 | } |
| 42 | printf("\n\n"); |
| 43 | |
| 44 | // Print pyramid |
| 45 | printf("Number pyramid:\n"); |
| 46 | print_pyramid(5); |
| 47 | |
| 48 | // FizzBuzz |
| 49 | printf("\nFizzBuzz (1-20):\n"); |
| 50 | for (int i = 1; i <= 20; i++) { |
| 51 | if (i % 15 == 0) printf("FizzBuzz"); |
| 52 | else if (i % 3 == 0) printf("Fizz"); |
| 53 | else if (i % 5 == 0) printf("Buzz"); |
| 54 | else printf("%d", i); |
| 55 | printf(" "); |
| 56 | } |
| 57 | printf("\n"); |
| 58 | |
| 59 | return 0; |
| 60 | } |