Beginner

Control Flow in C

Learn how to direct program execution in C with if/else conditionals, switch statements, for and while loops, and loop control using break and continue

Control flow is what turns a list of statements into a program that makes decisions and repeats work. As a procedural, imperative language, C gives you a compact but complete set of control structures: if/else for branching, switch for multi-way dispatch, and for, while, and do/while for looping. These constructs map almost directly onto the machine instructions the compiler generates, which is part of why C remains the language of operating systems and embedded firmware.

One detail shapes everything about control flow in C: there is no dedicated boolean type at the language’s core. Instead, C treats any non-zero value as true and zero as false. A condition like if (count) is perfectly valid and is true whenever count is not zero. This is a direct consequence of C’s static, weak typing — integers and conditions are interchangeable, which is powerful but demands care.

In this tutorial you will learn how to branch with if/else if/else, dispatch with switch, iterate with all three loop forms, and steer loops using break, continue, and the ternary ?: operator. Every example compiles cleanly with gcc:14 and is ready to run in Docker.

Conditionals: if, else if, and else

The if statement evaluates an expression and runs a block when it is non-zero. Chaining with else if lets you test several conditions in order, and a final else handles everything that falls through.

Create a file named conditionals.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

int main(void) {
    int score = 78;

    if (score >= 90) {
        printf("Grade: A\n");
    } else if (score >= 80) {
        printf("Grade: B\n");
    } else if (score >= 70) {
        printf("Grade: C\n");
    } else {
        printf("Grade: F\n");
    }

    // C has no boolean type by default: non-zero is true, zero is false
    int remaining = 0;
    if (remaining) {
        printf("Items left to process\n");
    } else {
        printf("Nothing left to process\n");
    }

    // The ternary operator ?: is a compact conditional expression
    int n = -5;
    const char *sign = (n >= 0) ? "non-negative" : "negative";
    printf("%d is %s\n", n, sign);

    return 0;
}

With score equal to 78, the first two conditions are false and score >= 70 is true, so the program prints Grade: C. Because remaining is 0 (false), the else branch runs. The ternary operator chooses the second string since -5 >= 0 is false.

The switch statement

When you need to branch on the discrete value of an integer or character, switch is clearer than a long if/else if chain. Each case is a label; execution jumps to the matching label and then falls through to the next case unless you write break. This fall-through behavior is a classic C gotcha — and occasionally a useful feature, as the grouped vowel cases below show.

Create a file named switch_demo.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>

int main(void) {
    char grade = 'B';

    switch (grade) {
        case 'A':
            printf("Excellent\n");
            break;
        case 'B':
            printf("Good\n");
            break;
        case 'C':
            printf("Fair\n");
            break;
        default:
            printf("Needs improvement\n");
            break;
    }

    // Intentional fall-through: vowels share one action
    char letter = 'e';
    switch (letter) {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
            printf("'%c' is a vowel\n", letter);
            break;
        default:
            printf("'%c' is a consonant\n", letter);
            break;
    }

    return 0;
}

The first switch matches case 'B' and prints Good, then break exits. In the second switch, 'e' matches the empty case 'e', which falls through to the shared printf, reporting that it is a vowel.

Loops: for, while, and do-while

C offers three loop forms. The for loop bundles initialization, condition, and update into one header — ideal for counting. The while loop tests its condition before each iteration. The do/while loop tests after the body, so it always runs at least once.

Create a file named loops.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int main(void) {
    // for loop: sum the integers 1 through 5
    int sum = 0;
    for (int i = 1; i <= 5; i++) {
        sum += i;
    }
    printf("Sum 1..5 = %d\n", sum);

    // while loop: count down from 3
    int count = 3;
    while (count > 0) {
        printf("Countdown: %d\n", count);
        count--;
    }

    // do-while loop: runs the body before testing the condition
    int attempts = 0;
    do {
        attempts++;
        printf("Attempt %d\n", attempts);
    } while (attempts < 2);

    return 0;
}

The for loop accumulates 1+2+3+4+5 = 15. The while loop prints the countdown from 3 down to 1, stopping when count reaches 0. The do/while loop runs once, increments attempts to 1, and runs again to make attempts equal to 2 before the condition attempts < 2 becomes false.

Loop control with break and continue

break exits the nearest enclosing loop immediately, while continue skips the rest of the current iteration and jumps to the loop’s next step. They give you fine-grained control without resorting to flags or extra variables.

Create a file named loop_control.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

int main(void) {
    // continue: skip even numbers, print only odds from 1 to 9
    printf("Odd numbers: ");
    for (int i = 1; i <= 9; i++) {
        if (i % 2 == 0) {
            continue;
        }
        printf("%d ", i);
    }
    printf("\n");

    // break: stop searching once we find the first multiple of 7
    int found = -1;
    for (int i = 20; i < 40; i++) {
        if (i % 7 == 0) {
            found = i;
            break;
        }
    }
    printf("First multiple of 7 at or above 20: %d\n", found);

    return 0;
}

The first loop uses continue to skip every even i, leaving only the odd numbers 1, 3, 5, 7, and 9. The second loop walks upward from 20 and breaks the instant it hits 21, the first multiple of 7 in range.

Running with Docker

Compile and run each example inside the official GCC image — no local toolchain required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the official GCC image
docker pull gcc:14

# Compile and run the conditionals example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "gcc -o conditionals conditionals.c && ./conditionals"

# Compile and run the switch example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "gcc -o switch_demo switch_demo.c && ./switch_demo"

# Compile and run the loops example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "gcc -o loops loops.c && ./loops"

# Compile and run the loop control example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "gcc -o loop_control loop_control.c && ./loop_control"

Expected Output

Running conditionals:

Grade: C
Nothing left to process
-5 is negative

Running switch_demo:

Good
'e' is a vowel

Running loops:

Sum 1..5 = 15
Countdown: 3
Countdown: 2
Countdown: 1
Attempt 1
Attempt 2

Running loop_control:

Odd numbers: 1 3 5 7 9 
First multiple of 7 at or above 20: 21

Key Concepts

  • Truth is numeric — C has no built-in boolean; any non-zero value is true and zero is false, so if (count) is idiomatic. (C99’s <stdbool.h> adds bool, true, and false as conveniences.)
  • else if is just nesting — C has no elif keyword; chained conditions are an if inside the previous else, tested top to bottom until one matches.
  • switch falls through — without a break, execution continues into the next case. Forgetting break is a common bug, but deliberate fall-through can group cases neatly.
  • Three loops, three timingsfor is best for counting, while tests before the body, and do/while guarantees at least one execution by testing after.
  • break and continue steer loopsbreak leaves the loop entirely; continue skips to the next iteration. Both act on the nearest enclosing loop only.
  • The ternary ?: is an expression — unlike if, it yields a value, making it handy for concise assignments like max = (a > b) ? a : b.
  • Braces prevent surprises — always brace your conditional and loop bodies; a single unbraced statement is legal but a frequent source of bugs when code is later edited.

Running Today

All examples can be run using Docker:

docker pull gcc:14
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining