Control Flow

Zig control flow is visible and value-producing: if, switch, while, and for are expressions, not just statements. The compiler enforces exhaustiveness and — in safe builds — never lets a check slip past at runtime.

if Expressions

if does not need parentheses in Zig. Every branch is a block expression: the value of the taken branch becomes the value of the whole if, which is how Zig avoids the ternary-operator mess.

if-else decision flow

Figure 1 — if/else decision: one branch is always taken; the result is a value.

Basics

const grade: u8 = 75;
const label = if (grade >= 60) "pass" else "fail";
// if-else as an expression — no ternary, just an if that yields a value
std.debug.print("{s}\n", .{label});

Unwrapping Optionals

Of special note: if can capture the value inside an optional in one step; you will formalize this in the optionals lesson.

const maybe: ?u32 = 42;
if (maybe) |value| {
    std.debug.print("value is {d}\n", .{value}); // captured, safe
} else {
    std.debug.print("no value\n", .{});
}

switch Expressions

switch compares a value against prongs. It is guaranteed exhaustive: the compiler refuses a switch that could miss a case, which eliminates entire bug families around missed enum values.

Basics and Exhaustiveness

const kind = 2;
const name = switch (kind) {
    0 => "zero",
    1 => "one",
    else => "many", // else covers everything else — required here
};
std.debug.print("{s}\n", .{name});
switch fan-out flow

Figure 2 — switch fans out to exactly one prong; exhaustive switching means no case is forgotten.

Switching on Enums

With enumerations the compiler knows every possible value, so the else prong becomes unnecessary and a forgotten case is a compile error. You will build the full picture in the structures lesson.

while Loops

while repeats its block as long as the condition is true. Two Zig extensions make it far more expressive than C: a continue expression that runs after each iteration, and direct support for optional and error-union conditions.

Basics

var i: u32 = 0;
while (i < 5) {
    std.debug.print("{d}\n", .{i});
    i += 1;
}

Continue Expression

var sum: u32 = 0;
var n: u32 = 0;
// the : (n += 1) part runs after each body iteration, before rechecking
while (n < 5) : (n += 1) {
    sum += n;
}
// sum == 10
while loop flow

Figure 3 — the condition guards every entry to the loop body.

while with Error Unions

When the condition is an error-union expression, while captures the unwrapped value; an error terminates the loop, and an else prong can handle it. This pattern drives streaming readers.

var lines_read: u32 = 0;
// readLine returns an error union; the while loop unwraps each success
while (readLine()) |line| {
    _ = line;
    lines_read += 1;
} else |err| {
    std.debug.print("stopped with error: {s}\n", .{@errorName(err)});
}

for Loops

for iterates over arrays and slices directly. It cannot run away into a manual index — the iteration is bounded by the collection itself, and you can capture the index as a second variable.

Basics

const names = [_][]const u8{ "Ada", "Guido", "Kern" };
for (names) |name| {
    std.debug.print("{s}\n", .{name});
}

Indexed Iteration

const squares = [_]u32{ 1, 4, 9, 16 };
for (squares, 0..) |square, index| {
    std.debug.print("[{d}] = {d}\n", .{ index, square });
}

Break, Continue, and Labels

Inside loops, break exits and continue jumps to the next iteration. Labels name a surrounding loop so a nested break or continue targets the right one — Zig rejects ambiguous jumps.

Labels

outer: for (0..5) |x| {
    for (0..5) |y| {
        if (y == 2) continue :outer; // skip to next x
        if (x == 4) break :outer;    // leave both loops
    }
}

defer — Cleanup Control Flow

A special, purely-Zig form of control flow: defer schedules cleanup to run when the surrounding scope exits, even on early returns. It is the backbone of resource management and is studied in the functions lesson.

Common Pitfalls

Missing else in Expressions

An if used as an expression must have both branches; a bare statement if needs none. Mixing the two confuses newcomers — decide up front whether you are computing a value or just acting.

Runaway while Loops

while has no automatic counter; forgetting the continue expression leaves a busy loop. In safe builds a runaway loop is diagnosable; better, test your loops with zig test (see the testing lesson).

Next: Functions & Cleanup — parameters, returns, defer, and errdefer.