While

for Zig 0.15.2 Buy

While

To build a loop using while, we use the following structure:


while (boolean condition){

     // Code executed repeatedly as long as the condition holds

} 


The block of code inside the braces will run over and over, as long as the boolean condition is true:

The while loop is widely used when the number of iterations is unknown. However, if the number of iterations is known, it’s common to use a counter variable that’s incremented (or decremented) in each iteration to control the exit condition:

  // counter for the index

  var n_i: usize = 0;

  // iterate while n_i is less than or equal to 10

  while (n_i <= 10) {

    // execute instructions and increment n_i

    n_i += 1;

It’s not the only way to control a while loop, but it’s very common. In Zig, to avoid writing the increment inside the block, we can do it more compactly like this:

  var n_i: usize = 0;

  // iterate while n_i is less than or equal to 10

  while (n_i <= 10):(n_i += 1) {

    // execute instructions but no incrementing n_i inside

In Zig, it’s possible-but not required-to extend a while loop with an else block:


while (boolean condition) : (optional increment) {

     // Code executed repeatedly as long as the condition is true

} else {

     // Code executed only once when the condition is no longer true

}


The else block of a while runs always and exactly once, either:

That said, we can interrupt the while loop at any time using the keyword break. This will stop the loop immediately, without even entering the else block (if it exists).

And if we want to jump straight to the beginning of the loop, skipping the rest of the block, we can use the keyword continue.


while (boolean condition){

     // to jump immediately to the start of the loop, use continue

     // to exit the loop immediately, use break

} else {

     // executed only once when the condition is not met, and if break wasn’t used

}


In the following example, the player is searching the lockers in the lab for any healing potions that might help recover from the enemy attack.

They know that:

We’re only going to list the potions that actually have healing effects:

while.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    // different magical potions

    const a_lockers =

       [_] // we don't specify the exact number

       []const u8{ // names as literal strings

          "grandma’s syrup",     // 0  

          "barbecue sauce",      // 1  

          "lemonade",            // 2  

          "tabasco",             // 3  

          "mayonnaise",          // 4  

          "ketchup",             // 5

         // ...

    };

    print("Healing potions in the lockers:\n", .{});

    // index counter

    var n_i: usize = 0;

    // scanning the inventory in search of healing potions:

    while (n_i < a_lockers.len) : (n_i += 1) {

        // retrieve the potion at the current index

        const s_potion = a_lockers[n_i];

        // if it’s barbecue sauce, skip it

        if (n_i == 1) continue;

        // if it’s Tabasco, exit the inventory

        // after Tabasco, it’s all just food

        if (n_i == 3) break;

        print("\tLocker {}: {s}\n", .{ n_i + 1, s_potion });

    }

}

$ zig run while.zig

Healing potions in the lockers:

        Locker 1: grandma’s syrup

        Locker 3: lemonade

In the example:

Infinite while loop

Earlier, when we talked about recursive functions, we saw that if a function keeps calling itself without stopping, it breaks the program: each call consumes a bit of reserved memory, and eventually that memory runs out. As we saw, this memory usage occurs even when no parameters are passed to the calls.

So… what happens with a while that never ends?

while_infinite.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    print("before the while\n", .{});

    while (true) {

        // nothing here for now

    }

    print("We never get here\n", .{});

}

~$ zig run while_infinite.zig

before the while

Nothing. Literally nothing happens. An infinite while by itself doesn’t consume more resources with each iteration - here it just spins in place, waiting to be interrupted. To stop it, you can press Ctrl+C, which sends an interrupt signal to the program from the terminal.

Obviously, if inside the loop there's code that consumes resources without releasing them - like allocating memory, opening files without closing them, etc. you can crash the program. But on its own, a while loop won’t do that.

While with optional value capture

In Zig, there’s a concept we’re introducing now because it works especially well with while loops: optional value capture.

An optional value in Zig can either hold a concrete value or be null, meaning nothing; it holds nothing.

Because of that, its type doesn’t directly match the type of the values we expect to work with. It’s like a possibly empty container. Why and how do we need to deal with this?

The player enters a room and finds mysterious crates containing pure energy. Some of these crates, unfortunately, contain an absolute void - their value is literally null. When the player opens a crate, they might find a positive number - the stored energy - or… nothing. But that “nothing” is not a 0. It’s not a number or any value at all… so what happens?

while_unwrap_null.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]u8{

        100,

        10,

        null, // empty crate

        30,

        120,

        null, // empty crate

    };

    var n_i: usize = 0;

    while (a_boxes[n_i]) |n_pts| : (n_i += 1) {

        print("{}. You’ve gained {} energy points!\n", 

        .{ n_i + 1, n_pts });

    } else {

        print("{}. Empty crate! \n", .{n_i + 1});

    }

}

$ zig run while_unwrap_null.zig

while_unwrap_null.zig:8:9: error: expected type 'u8', found '@TypeOf(null)'

        null, // empty crate

        ^~~~

The program has “exploded.” Zig expected the elements of the a_boxes array to be of type u8, but suddenly the player “opens” a box, and the program encounters a null. That’s not a number. It’s nothing - and that’s why it fails at compile time.

To handle a value that might be null, we need to define those elements by marking their type with a ? in front of the usual type. In this case, it will be ?u8:

while_unwrap_null_fix.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]?u8{ // <- optional type indicated

        100,

        10,

        null, // empty crate

        30,

        120,

        null, // empty crate

    };

    var n_i: usize = 0;

    while (a_boxes[n_i]) |n_pts| : (n_i += 1) {

        print("{}. You got {} energy points!\n", 

        .{ n_i + 1, n_pts });

    } else {

        print("{}. Empty crate! \n", .{n_i + 1});

    }

}

$ zig run while_unwrap_null_fix.zig

1. You got 100 energy points!

2. You got 10 energy points!

3. Empty crate!

The program no longer crashes. However, since the while loop with capture stops when it hits a null, everything halts at the third crate. Not bad, but it can be improved. The player wants to open all the crates. What can we do to iterate through and process all the elements?

This is a good chance to use what we already know about switch: label jumps, and combine it with what we’ve learned about while. Yes, it does make things a bit more complex, we’ll see a simpler way later, but using continue :label value is pretty fun, don’t you think?

while_unwrap.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]?u8{ // optional type

        100,

        10,

        null, // empty crate

        30,

        120,

        null,// empty crate

    };

    var n_i: usize = 0;

    loop: switch (n_i) {

        else => {

            while (a_boxes[n_i]) |n_pts| : (n_i += 1) {

                print("{}. You got {} energy points!\n", 

                .{ n_i + 1, n_pts });

            } else {

                print("{}. Empty crate! \n", .{n_i + 1});

                n_i += 1;

                if (n_i < a_boxes.len) continue :loop n_i;

            }

        },

    }

}

$ zig run while_unwrap.zig

1. You got 100 energy points!

2. You got 10 energy points!

3. Empty crate!

4. You got 30 energy points!

5. You got 120 energy points!

6. Empty crate!

Now the player opens all the crates, and the program can handle both expected values and nulls.

        Jumping to labels is fun, I admit it. But most of the time, there’s a simpler way. If you ever feel the urge (as I do), just remember there’s probably a jump-free solution… even if it’s more boring:

while_unwrap_2.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]?u8{

        100,

        10,

        null,

        30,

        120,

        null,

    };

    var n_i: usize = 0;

    while (n_i < a_boxes.len) : (n_i += 1) {

        if (a_boxes[n_i]) |n_pts|

            print("{}. You got {} energy points!\n", 

            .{ n_i + 1, n_pts })

        else

            print("{}. Empty crate! \n", .{n_i + 1});

    }

}

$ zig run while_unwrap_2.zig

1. You got 100 energy points!

2. You got 10 energy points!

3. Empty crate!

4. You got 30 energy points!

5. You got 120 energy points!

6. Empty crate!

What matters is exploring the language you’re using and trying to “break” it. If one day you feel like writing an entire game using only switch - why not? Programming languages are instruments: just like a guitarist or pianist has to know the limits of their instrument, a good programmer should make the languages they use their own. Trying weird or unorthodox things will help you understand how far you can push.


Recursive functions
For
© 2025 - 2026 Zen of Zig