Zig While Loop

A while loop repeats a block of code as long as a condition stays true. When the condition becomes false, the loop stops. Zig's while loop handles the most common looping patterns cleanly, and it also serves as Zig's only general-purpose loop (unlike C, Zig has no traditional C-style for loop with init-condition-increment).

Basic While Loop

var count: u32 = 1;

while (count <= 5) {
    std.debug.print("Count: {d}\n", .{count});
    count += 1;
}
  count = 1
    |
  [ 1 <= 5 ? ] → true  → print "Count: 1" → count = 2
  [ 2 <= 5 ? ] → true  → print "Count: 2" → count = 3
  [ 3 <= 5 ? ] → true  → print "Count: 3" → count = 4
  [ 4 <= 5 ? ] → true  → print "Count: 4" → count = 5
  [ 5 <= 5 ? ] → true  → print "Count: 5" → count = 6
  [ 6 <= 5 ? ] → false → loop ends

Output:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

While with a Continue Expression

Zig's while loop accepts an optional continue expression — code that runs after every iteration, just before checking the condition again. This replaces the third part of a C-style for loop:

var i: u32 = 0;

while (i < 5) : (i += 1) {
    std.debug.print("i = {d}\n", .{i});
}
  Structure:
  while (condition) : (continue_expression) {
      body
  }

  Execution order per loop:
  1. Check condition
  2. Run body (if true)
  3. Run continue expression
  4. Go back to step 1

The continue expression runs even when you use continue to skip the rest of the body. This guarantees the loop variable always advances, preventing accidental infinite loops.

Infinite Loop with break

A while loop with true as its condition runs forever until a break statement exits it:

var attempts: u8 = 0;

while (true) {
    attempts += 1;
    std.debug.print("Attempt {d}\n", .{attempts});

    if (attempts >= 3) {
        std.debug.print("Max attempts reached.\n", .{});
        break;
    }
}
  [always true]
       |
  attempt 1 → continue
  attempt 2 → continue
  attempt 3 → break → exit loop

This pattern is common for event loops, retry logic, and menus where the exit condition is complex and determined inside the loop body.

Continue — Skip an Iteration

continue jumps immediately to the continue expression (or back to the condition check if there is none), skipping the remaining code in the current iteration:

var n: u32 = 0;

while (n < 10) : (n += 1) {
    if (n % 2 == 0) continue;  // skip even numbers
    std.debug.print("{d} is odd\n", .{n});
}
  n = 0 → even → skip → n = 1
  n = 1 → odd  → print "1 is odd" → n = 2
  n = 2 → even → skip → n = 3
  n = 3 → odd  → print "3 is odd" → n = 4
  ... and so on

While with an Else Branch

Zig's while loop supports an else branch. This runs only when the loop condition becomes false naturally — not when the loop exits via break:

var x: u32 = 0;
const target: u32 = 5;

while (x < 10) : (x += 1) {
    if (x == target) {
        std.debug.print("Found target at {d}\n", .{x});
        break;
    }
} else {
    std.debug.print("Target not found in range.\n", .{});
}
  Loop runs:
  x=0,1,2,3,4 → x==5 → found → break → else does NOT run

  If target = 99:
  x=0..9 → condition fails → else runs → "Target not found"

This is Zig's clean alternative to setting a flag variable to track whether a loop found what it was looking for.

While with Optional Capture

When a function returns an optional value (a value that can be null), the while loop can keep iterating as long as the optional has a value:

// Simulated: reading items from a list one by one
var items = [_]?u32{10, 20, null, 30};
var idx: usize = 0;

while (idx < items.len) : (idx += 1) {
    if (items[idx]) |val| {
        std.debug.print("Item: {d}\n", .{val});
    }
}
  items: [ 10 | 20 | null | 30 ]
              ↓    ↓    ↓     ↓
           print print skip  print

Nested While Loops

var row: u8 = 1;
while (row <= 3) : (row += 1) {
    var col: u8 = 1;
    while (col <= 3) : (col += 1) {
        std.debug.print("{d}x{d}={d}  ", .{row, col, row * col});
    }
    std.debug.print("\n", .{});
}

Output (a 3x3 multiplication grid):

1x1=1  1x2=2  1x3=3
2x1=2  2x2=4  2x3=6
3x1=3  3x2=6  3x3=9

Breaking Out of Nested Loops with Labels

Zig lets you label a loop and break out of a specific outer loop from inside a nested one:

outer: while (true) {
    var n: u32 = 0;
    while (n < 5) : (n += 1) {
        if (n == 3) break :outer;  // exits the outer loop
        std.debug.print("n={d}\n", .{n});
    }
}
std.debug.print("Done.\n", .{});
  n=0 → print
  n=1 → print
  n=2 → print
  n=3 → break :outer → jumps out of both loops → "Done."

Common Mistake: Forgetting to Advance the Loop Variable

  WRONG:                        CORRECT:
  var i: u32 = 0;               var i: u32 = 0;
  while (i < 5) {               while (i < 5) : (i += 1) {
      // forgot i += 1              std.debug.print("{d}\n", .{i});
      // loops forever!         }
  }

Zig does not warn about infinite loops automatically. Always verify that your loop variable changes each iteration. Using the continue expression syntax : (i += 1) keeps the increment in one visible place and makes forgetting it much harder.

Leave a Comment

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