C Mastery / Control Flow: Selection (if, switch)
Part 1 — The Core Language

Control Flow: Selection (if, switch)

This chapter covers the two selection statements: if/else and switch. These are concise because the reader already knows them from other languages, but C has specific semantics worth pinning down.

Why This Matters

if and switch are straightforward, but switch has fall-through semantics and integer-only conditions that surprise programmers coming from languages with richer switch statements.

Prerequisites

Core Concept

if / else

if (condition)
    statement;
else
    statement;

condition is any scalar expression. It is true if nonzero, false if zero. Each branch is a *single statement*; use a block { } for multiple statements.

The else binds to the nearest unmatched if (the "dangling else" rule).

switch

switch (expression) {
    case constant1:
        statements;
        break;
    case constant2:
        statements;
        break;
    default:
        statements;
        break;
}

The controlling expression must have integer type (after promotion). Each case label must be an integer constant expression. Execution jumps to the matching case and falls through subsequent cases unless a break (or return, goto) is reached.

Syntax

if (x > 0) {
    /* ... */
} else if (x < 0) {
    /* ... */
} else {
    /* ... */
}

switch (x) {
    case 0: /* ... */ break;
    case 1:
    case 2: /* shared handling for 1 and 2 */ break;
    default: /* ... */ break;
}

Examples

Dangling else

if (a)
    if (b)
        f();
    else
        g();   /* binds to the inner if */

The else binds to the nearest unmatched if (if (b)). To bind it to the outer if, use braces.

switch fall-through

#include <stdio.h>

int main(void)
{
    int x = 1;
    switch (x) {
        case 0: printf("zero\n"); break;
        case 1: printf("one\n");   /* no break: falls through */
        case 2: printf("one-or-two\n"); break;
        default: printf("other\n");
    }
    return 0;
}

Expected output:

one
one-or-two

How It Works

if compiles to a conditional branch. switch compiles to either a chain of comparisons or a jump table (for dense, small integer case values), which can be faster. The compiler decides the strategy.

Variations

switch on expressions

The controlling expression can be any integer expression, not just a variable:

switch (get_status()) {
    /* ... */
}

case ranges (non-standard extension)

GCC and Clang support case ranges as an extension (case 1 ... 5:), but this is not ISO C.

Common Mistakes

cannot precede a declaration in older C; wrap in a block).

Undefined Behavior

statement simply completes. The controlling expression must be a valid integer expression (any UB in evaluating it propagates).

Portability

Under the Hood

For dense case values, compilers emit a jump table (an array of target addresses indexed by the value). For sparse values, they emit a decision tree or a sequence of comparisons. This is an optimization detail, not a language feature.

Practical Usage

machines).

Exercises

1. Write a program that maps an integer day number (0–6) to a day name using switch, with a default for invalid input. 2. Demonstrate fall-through and then show how to avoid it. 3. Explain the dangling-else problem and write a corrected version.

Deep Challenge

Implement a small integer state machine using switch and an enum, with explicit fall-through where appropriate. Justify why switch (rather than a chain of ifs) is the better choice for this design.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.flow.if05
c.flow.switch05