switch
An if statement is good for general conditions:
if (age >= 18) {
// ...
}But sometimes you are not testing ranges or complex logic. You are matching one value against several exact cases.
For example:
- a menu choice
- a command name
- a keyboard key
- an enum value
- a token type in a parser
This is where switch is useful.
A switch compares one value against multiple possible patterns.
Your First switch
const std = @import("std");
pub fn main() void {
const day = 3;
switch (day) {
1 => std.debug.print("Monday\n", .{}),
2 => std.debug.print("Tuesday\n", .{}),
3 => std.debug.print("Wednesday\n", .{}),
else => std.debug.print("Unknown day\n", .{}),
}
}This program prints:
WednesdayThe value being checked is:
dayZig compares it against each case:
1 => ...
2 => ...
3 => ...When Zig finds a matching case, it runs that branch.
If nothing matches, the else branch runs.
The Shape of a switch
The general shape is:
switch (value) {
pattern1 => expression1,
pattern2 => expression2,
else => fallback_expression,
}Each branch uses:
pattern => resultThe arrow => means “if this pattern matches, use this result.”
switch Is an Expression
Like if, switch can produce a value.
const std = @import("std");
pub fn main() void {
const day = 2;
const name = switch (day) {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
else => "Unknown",
};
std.debug.print("{s}\n", .{name});
}This prints:
TuesdayThe whole switch expression becomes one value.
If day is 2, the result becomes:
"Tuesday"That value is stored in name.
This style is very common in Zig.
switch Does Not Fall Through
In C, switch statements can “fall through” accidentally.
Example C code:
switch (x) {
case 1:
printf("one\n");
case 2:
printf("two\n");
}If x is 1, this prints both:
one
twobecause execution falls into the next case unless you write break.
Zig does not work this way.
In Zig, each branch is separate and isolated.
switch (x) {
1 => std.debug.print("one\n", .{}),
2 => std.debug.print("two\n", .{}),
else => {},
}Only one branch runs.
This removes a very common source of bugs.
Multiple Patterns in One Branch
You can combine several patterns.
const std = @import("std");
pub fn main() void {
const c = 'a';
switch (c) {
'a', 'e', 'i', 'o', 'u' => {
std.debug.print("vowel\n", .{});
},
else => {
std.debug.print("not vowel\n", .{});
},
}
}This prints:
vowelThe branch runs if any listed pattern matches.
Ranges in switch
You can also match ranges.
const std = @import("std");
pub fn main() void {
const score = 85;
switch (score) {
90...100 => std.debug.print("grade A\n", .{}),
80...89 => std.debug.print("grade B\n", .{}),
70...79 => std.debug.print("grade C\n", .{}),
else => std.debug.print("lower grade\n", .{}),
}
}This prints:
grade BThe syntax:
80...89means “all integers from 80 through 89 inclusive.”
Ranges make some switch statements much cleaner than long if else if chains.
Blocks Inside Branches
A branch can contain a block.
const std = @import("std");
pub fn main() void {
const command = 1;
switch (command) {
1 => {
std.debug.print("open file\n", .{});
std.debug.print("loading data\n", .{});
},
2 => {
std.debug.print("save file\n", .{});
},
else => {
std.debug.print("unknown command\n", .{});
},
}
}Use blocks when the branch needs multiple statements.
Matching Enums
switch becomes especially powerful with enums.
const std = @import("std");
const Direction = enum {
north,
south,
east,
west,
};
pub fn main() void {
const dir = Direction.east;
switch (dir) {
.north => std.debug.print("up\n", .{}),
.south => std.debug.print("down\n", .{}),
.east => std.debug.print("right\n", .{}),
.west => std.debug.print("left\n", .{}),
}
}This prints:
rightNotice the enum values:
.north
.south
.east
.westInside the switch, Zig already knows the type is Direction, so the enum name can be omitted.
Exhaustive Checking
One of Zig’s best features is exhaustive checking.
If every enum case is handled, Zig knows the code is complete.
Example:
const Direction = enum {
north,
south,
};
fn move(dir: Direction) void {
switch (dir) {
.north => {},
.south => {},
}
}This is complete.
But suppose you later add:
eastNow the compiler reports an error because the switch no longer handles every possibility.
This is extremely valuable in large programs. The compiler helps you update all affected code safely.
Using else
You can use else as a fallback branch.
switch (value) {
1 => {},
2 => {},
else => {},
}But when switching over enums, many Zig programmers prefer exhaustive handling without else.
Why?
Because if you add a new enum value later, the compiler forces you to update the switch.
Example:
const State = enum {
idle,
running,
stopped,
};
switch (state) {
.idle => {},
.running => {},
.stopped => {},
}This is safer than:
switch (state) {
.idle => {},
else => {},
}The second version silently hides future enum values inside else.
Capturing Values
switch can capture matched values.
const std = @import("std");
const Token = union(enum) {
number: i32,
word: []const u8,
};
pub fn main() void {
const token = Token{ .number = 42 };
switch (token) {
.number => |n| {
std.debug.print("number: {}\n", .{n});
},
.word => |w| {
std.debug.print("word: {s}\n", .{w});
},
}
}This prints:
number: 42This syntax:
.number => |n|means:
- match the
.numbercase - extract the stored value
- name it
n
This feature is very important when working with tagged unions.
switch and Types
All branches in a value-returning switch must produce compatible types.
This is valid:
const result = switch (x) {
1 => "one",
2 => "two",
else => "other",
};All branches return strings.
This is invalid:
const result = switch (x) {
1 => 123,
else => "hello",
};One branch returns an integer. The other returns a string.
Zig requires a consistent result type.
switch vs if
Use if when:
- conditions are unrelated
- conditions involve comparisons
- conditions involve boolean logic
Example:
if (x > 0 and x < 100) {
// ...
}Use switch when:
- matching one value
- handling enums
- handling many exact cases
- matching ranges cleanly
Example:
switch (command) {
.open => {},
.save => {},
.quit => {},
}Empty Branches
Sometimes a branch intentionally does nothing.
switch (value) {
0 => {},
else => std.debug.print("not zero\n", .{}),
}The empty block:
{}means “do nothing.”
The Main Idea
A switch compares one value against multiple patterns.
It is cleaner and safer than long chains of if else if when many exact cases exist.
Zig’s switch has several important properties:
- no accidental fallthrough
- exhaustive enum checking
- ranges and multiple patterns
- value-returning expressions
- pattern matching with captured values
You will use switch constantly in real Zig programs, especially when working with enums, parsers, protocols, compilers, and state machines.