In Zig, we create a for loop like this:

for Zig 0.15.2 Buy

For

         For loops are ideal for cases where the number of iterations is known in advance, like when processing an array or when we want to do something a fixed number of times.

In Zig, we create a for loop like this:


for (sequence_of_elements) | element | {

     // Code executed for each element in the processed sequence

} 


We can also add a range to get the index of each element:


for (sequence_of_elements, 0..) | element, index | {

     // executed for each element in the processed sequence

} 


Just like we can combine an array with a range, we can also combine multiple arrays. Imagine that in your game, the player arrives at the space base where the best shmup combat models are available, and you want to display the data on screen.

The problem is that the base terminal uses an API that’s a bit… “unfriendly” for Earthlings: the data for the same ship comes in different arrays - one for the model, another for attack power, another for speed.

Luckily, you have Zig’s for loops:

for_spaceships.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_spaceship = [_][]const u8{ "Fighter", "VViper", "Silver Hawk", "Saint Dragon", "Arrowhead" };

    const a_attack = [_]u16{

       200, // single shot - can be double

       450, // with powerups

       400, // shot + missiles + laser

       375, // frontal shot with powerups

       600, // powerful laser + Force capsule

   };

    const a_speed = [_]u16{

       150, // slow

       300, // fast with powerups

       275, // medium speed

       200, // slow mecha due to articulated tail

       180, // slow

    };

    print("Most popular combat models: \n", .{});

    print("-------------------------------------------------\n", .{});

    print("#\t| MODEL\t\t| ATTACK\t| SPEED\t \n", .{});

    print("-------------------------------------------------\n", .{});

    for (a_spaceship, a_attack, a_speed, 0..) // loop to list combined arrays

    | // data capture

       s_model,    // ship model

       n_pow,      // attack points

       n_speed,    // ship speed

       n_i,        // index

    | {

        print("{}\t| {s}\t| {}\t\t| {}\t \n", // print the data

            .{ n_i + 1, s_model, n_pow, n_speed });

    }

}

$ zig run for_spaceships.zig

Most popular combat models:

-------------------------------------------------

#      | MODEL         | ATTACK        | SPEED    

-------------------------------------------------

1      | Fighter       | 200           | 150    

2      | VViper        | 450           | 300    

3      | Silver Hawk   | 400           | 275    

4      | Saint Dragon  | 375           | 200    

5      | Arrowhead     | 600           | 180  

Excellent! With this combined information, the player can continue their space adventure. In a single for loop, we combined the names, power, and speed of the ships in the catalog, plus an automatic index. In this case, the elements are traversed in parallel: one position from each array forms a group together with the same position in the other arrays.

Now, after listing the ships, the player enters the menu: “Power-ups for the ship.” But here we run into a problem: the database is a mess. Ugh:

The power-ups are mixed - they have different properties: attack, defense, and healing points, but the fields that don’t apply to a given type are set to null.

Those should be printed in the list as value 0.

for_powerups.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_powerups = [_]?[]const u8{

        "Turbo Boost", // increases speed

        "Double Laser", // attack

        null, // DEACTIVATED !!!

        null, // CORRUPT DATA !!!

        "Shield Field", // defense only

        "Emergency Kit", // restores energy

        null, // Name unknown but still valid

        "OverdriveCore", // mega power-up

        "Deleted",

        "Deleted",

        // ...and so on for the rest

    };

    const a_pow_attack = [_]?u16{

        null, // Turbo Boost gives no attack

        80, // Double Laser

        null,

        777,

        null, // Shield Field gives no attack

        null, // Emergency Kit is not offensive

        null,

        180, // Overdrive Core

        null,

        null,

        // ...

    };

    const a_pow_def = [_]?u16{

        5, // Turbo Boost gives some defense

        null, // Double Laser gives no defense

        null,

        777,

        40, // Shield Field

        null, // Emergency Kit

        null, // ?

        20, // Overdrive Core

        null,

        null,

        // ...

    };

    const a_pow_heal = [_]?u16{

        null, // no healing

        null, // no healing

        null,

        777,

        null, // Shield Field doesn’t heal

        100, // Emergency Kit heals!

        200, // ? This was likely a healing item

        null, // Overdrive Core

        null,

        null,

        // ...

    };

    print("Power ups for the ship: \n", .{});

    print("======================================================\n", .{});

    print("#\t| NAME\t\t| ATK.\t| DEF.\t| HP.\t \n", .{});

    print("======================================================\n", .{});

    var n_i: usize = 0;

    loop: switch (n_i) {

        else => {

            for (a_powerups[n_i..], 

                 a_pow_attack[n_i..], 

                 a_pow_def[n_i..], 

                 a_pow_heal[n_i..], 

                 n_i..) // loop to list the combined arrays

                | // data capture

                  s_name, // ship model

                  n_att, // attack points

                  n_def, // defense points

                  n_energy, // energy points

                  n_ix, // index

                | {

                // index 2 is deactivated -> skip it

                if (n_ix == 2) continue;

                // database wiped

                // exit after record 8

                if (n_ix > 7) break;

                // if data is corrupt, all fields = 777

                if (n_att == 777) {

                    n_i = n_ix + 1;

                    continue :loop n_i;

                }

                // handle some null names

                const s_name_pow: []const u8 = s_name orelse "UNKNOWN";

                print("{}\t| {s}\t\t| {} \t| {}\t| {}\t  \n", //

                    .{

                        n_ix + 1, // index

                        s_name_pow, // name

                        n_att orelse 0, // attack points

                        n_def orelse 0, // defense points

                        n_energy orelse 0, // energy

                    });

            }

        },

    }

}

$ zig run for_powerups.zig

Power-ups for the ship:

======================================================

#       | NAME                  | ATK.  | DEF.  | HP.

======================================================

1       | Turbo Boost           | 0     | 5     | 0

2       | Double Laser          | 80    | 0     | 0

5       | Shield Field          | 0     | 40    | 0

6       | Emergency Kit         | 0     | 0     | 100

7       | UNKNOWN               | 0     | 0     | 200

8       | OverdriveCore         | 180   | 20    | 0

By using break and continue inside for loops, we can handle null values, exit the loop, jump to a label, return to the start of the loop, and, as you can see, process the data in whatever way suits us best.

If you look closely, there’s a detail we hadn’t seen until now: the keyword orelse, which is used to substitute optional (potentially null) values with default ones:

  // handling some null names

  const s_name_pow: []const u8 = s_name orelse "UNKNOWN";

This means that when s_name is null, the string “UNKNOWN” is assigned to the constant s_name_pow, and when it’s not null, the actual value it contains - a valid string - is assigned.

The same applies when printing optional values:

  n_att orelse 0, // attack points

  n_def orelse 0, // defense points

  n_energy orelse 0, // energy

If any of these is null, it’s replaced by 0. If not, their actual numeric value is used. If these variables have the value null, we pass 0 as the argument to print. This way, we can print potentially null values as regular integers without needing to use {?}:

      // without orelse (would fail if there are nulls)

  print("{}\t| {s}\t\t| {?} \t| {?}\t| {?}\t  \n"

      // with orelse

  print("{}\t| {s}\t\t| {} \t| {}\t| {}\t  \n"

While
Chapter summary
© 2025 - 2026 Zen of Zig