Zig Switch Statement

A switch statement picks one path from several options based on a single value. Instead of writing a long chain of if-else if conditions, you list every possible case in one structured block. Zig's switch is exhaustive — the compiler forces you to handle every possible value, so nothing slips through unhandled.

Basic Switch

const day: u8 = 3;

switch (day) {
    1 => std.debug.print("Monday\n", .{}),
    2 => std.debug.print("Tuesday\n", .{}),
    3 => std.debug.print("Wednesday\n", .{}),
    4 => std.debug.print("Thursday\n", .{}),
    5 => std.debug.print("Friday\n", .{}),
    6 => std.debug.print("Saturday\n", .{}),
    7 => std.debug.print("Sunday\n", .{}),
    else => std.debug.print("Invalid day\n", .{}),
}
  day = 3
    |
  +---+---+---+---+---+---+---+------+
  | 1 | 2 | 3 | 4 | 5 | 6 | 7 | else |
  +---+---+---+---+---+---+---+------+
              ↑
          Match! → "Wednesday"

Each case uses value => action syntax. The else branch catches anything that does not match any listed case. For integer types with a large range, else is required. For enums and booleans where all values are known, the compiler knows when every case is covered and does not require else.

Switch with Blocks

When a case needs more than one line of code, wrap it in braces:

const grade: u8 = 'B';

switch (grade) {
    'A' => {
        std.debug.print("Excellent!\n", .{});
        std.debug.print("Top of the class.\n", .{});
    },
    'B' => {
        std.debug.print("Good work.\n", .{});
        std.debug.print("Keep improving.\n", .{});
    },
    'C' => std.debug.print("Average.\n", .{}),
    else => std.debug.print("Unknown grade.\n", .{}),
}

Multiple Values Per Case

Group values that share the same outcome using a comma:

const month: u8 = 4;

switch (month) {
    1, 3, 5, 7, 8, 10, 12 => std.debug.print("31 days\n", .{}),
    4, 6, 9, 11            => std.debug.print("30 days\n", .{}),
    2                      => std.debug.print("28 or 29 days\n", .{}),
    else                   => std.debug.print("Invalid month\n", .{}),
}
  month = 4
      |
  [ 1,3,5,7,8,10,12 ? ] → no
  [ 4,6,9,11 ?        ] → yes → "30 days"

Range Matching

Zig supports inclusive ranges inside switch cases using start...end:

const score: u32 = 82;

switch (score) {
    90...100 => std.debug.print("Grade A\n", .{}),
    75...89  => std.debug.print("Grade B\n", .{}),
    60...74  => std.debug.print("Grade C\n", .{}),
    0...59   => std.debug.print("Grade F\n", .{}),
    else     => std.debug.print("Invalid score\n", .{}),
}
  score = 82
      |
  [ 90-100 ? ] → no
  [ 75-89  ? ] → yes → "Grade B"

The range 75...89 includes both 75 and 89 and every number between them. Ranges must not overlap — if two ranges cover the same number, the compiler reports an error.

Switch as an Expression

Like if, a switch can return a value. Assign the result directly to a variable:

const season: u8 = 7; // July

const season_name = switch (season) {
    12, 1, 2  => "Winter",
    3, 4, 5   => "Spring",
    6, 7, 8   => "Summer",
    9, 10, 11 => "Autumn",
    else      => "Unknown",
};

std.debug.print("Season: {s}\n", .{season_name});
  season = 7
      |
  switch returns one string
      |
  season_name = "Summer"

Every branch must return the same type. Zig enforces this at compile time.

Switch on Enums

Switch works especially well with enums because the compiler knows every possible value. No else is needed when all variants are covered:

const Direction = enum { North, South, East, West };

const dir = Direction.East;

switch (dir) {
    .North => std.debug.print("Going North\n", .{}),
    .South => std.debug.print("Going South\n", .{}),
    .East  => std.debug.print("Going East\n", .{}),
    .West  => std.debug.print("Going West\n", .{}),
}
  Compass:
        North
          |
  West ---+--- East   ← matched
          |
        South

  dir = .East → "Going East"

If you add a new variant to the enum later and forget to update the switch, the compiler immediately reports an error. This exhaustiveness check prevents a whole class of bugs where new states go unhandled silently.

Inline Switch

Zig supports inline switch, which unrolls the switch at compile time. Each branch gets the case value as a compile-time constant. This is useful for generic code that behaves differently per type or value:

const x: u8 = 2;

inline switch (x) {
    1 => compileTimeAction(1),
    2 => compileTimeAction(2),
    3 => compileTimeAction(3),
    else => {},
}

Switch vs If-Else Chain

  If-else chain:             Switch:
  +-----------------+        +------------------+
  | if (x == 1) {}  |        | switch (x) {     |
  | else if x==2 {} |        |   1 => {},       |
  | else if x==3 {} |        |   2 => {},       |
  | else if x==4 {} |        |   3 => {},       |
  | else {}         |        |   4 => {},       |
  +-----------------+        |   else => {},    |
                             | }                |
                             +------------------+

  Switch is:
  - Easier to read with many cases
  - Checked for exhaustiveness
  - Supports ranges and multi-value cases

Use switch when you match a single value against several known options. Use if-else when your conditions involve different variables or complex expressions that do not reduce to matching one value.

Practical Example: HTTP Status Handler

const std = @import("std");

pub fn main() void {
    const status_code: u16 = 404;

    const message = switch (status_code) {
        200       => "OK",
        201       => "Created",
        301, 302  => "Redirect",
        400       => "Bad Request",
        401       => "Unauthorized",
        403       => "Forbidden",
        404       => "Not Found",
        500       => "Server Error",
        else      => "Unknown Status",
    };

    std.debug.print("HTTP {d}: {s}\n", .{status_code, message});
}

Output:

HTTP 404: Not Found

Leave a Comment

Your email address will not be published. Required fields are marked *