Skip to content

Zig Loops

Loops are fundamental control structures in programming. This chapter will cover various loop statements in Zig in detail.

while Loops

Basic while Loop

The while loop repeatedly executes a code block while a condition is true:

zig
const std = @import("std");

pub fn main() void {
    var i: i32 = 0;
    
    std.debug.print("Basic while loop:\n", .{});
    while (i < 5) {
        std.debug.print("i = {}\n", .{i});
        i += 1;
    }
}

while Loop with Continue Expression

Zig's while loop can include a continue expression that executes at the end of each iteration:

zig
const std = @import("std");

pub fn main() void {
    var i: i32 = 0;
    
    std.debug.print("while loop with continue expression:\n", .{});
    while (i < 5) : (i += 1) {
        std.debug.print("i = {}\n", .{i});
    }
}

while Loop with Optional Values

while loops can handle optional values, ending when the value is null:

zig
const std = @import("std");

fn getNextNumber(current: i32) ?i32 {
    if (current >= 5) return null;
    return current + 1;
}

pub fn main() void {
    var maybe_number: ?i32 = 0;
    
    std.debug.print("while loop with optional values:\n", .{});
    while (maybe_number) |number| {
        std.debug.print("Number: {}\n", .{number});
        maybe_number = getNextNumber(number);
    }
}

while Loop with Error Handling

while loops can also handle error union types:

zig
const std = @import("std");

const NumberError = error{
    TooLarge,
};

fn getNextSafeNumber(current: i32) NumberError!?i32 {
    if (current >= 10) return NumberError.TooLarge;
    if (current >= 5) return null;
    return current + 1;
}

pub fn main() void {
    var maybe_number: NumberError!?i32 = 0;
    
    std.debug.print("while loop with error handling:\n", .{});
    while (maybe_number) |maybe_num| {
        if (maybe_num) |number| {
            std.debug.print("Safe number: {}\n", .{number});
            maybe_number = getNextSafeNumber(number);
        } else {
            break;
        }
    } else |err| {
        std.debug.print("Encountered error: {}\n", .{err});
    }
}

for Loops

Iterating Over Arrays

for loops are primarily used to iterate over arrays, slices, and other iterable objects:

zig
const std = @import("std");

pub fn main() void {
    const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
    std.debug.print("Iterating over array:\n", .{});
    for (numbers) |number| {
        std.debug.print("Number: {}\n", .{number});
    }
}

Iterating Over Slices

zig
const std = @import("std");

pub fn main() void {
    const fruits = [_][]const u8{ "Apple", "Banana", "Orange", "Grape" };
    const slice = fruits[1..3]; // Banana, Orange
    
    std.debug.print("Iterating over slice:\n", .{});
    for (slice) |fruit| {
        std.debug.print("Fruit: {s}\n", .{fruit});
    }
}

for Loop with Index

You can get both the index and value simultaneously:

zig
const std = @import("std");

pub fn main() void {
    const colors = [_][]const u8{ "Red", "Green", "Blue" };
    
    std.debug.print("for loop with index:\n", .{});
    for (colors, 0..) |color, index| {
        std.debug.print("Index {}: {s}\n", .{ index, color });
    }
}

Iterating Over Strings

zig
const std = @import("std");

pub fn main() void {
    const text = "Hello";
    
    std.debug.print("Iterating over string:\n", .{});
    for (text) |char| {
        std.debug.print("Character: {c} (ASCII: {})\n", .{ char, char });
    }
}

Range Loops

You can use range syntax to create loops:

zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Range loop (0 to 4):\n", .{});
    for (0..5) |i| {
        std.debug.print("i = {}\n", .{i});
    }
    
    std.debug.print("Range loop (2 to 6):\n", .{});
    for (2..7) |i| {
        std.debug.print("i = {}\n", .{i});
    }
}

Loop Control Statements

break Statement

break is used to exit a loop early:

zig
const std = @import("std");

pub fn main() void {
    var i: i32 = 0;
    
    std.debug.print("Loop with break:\n", .{});
    while (true) {
        if (i >= 3) break;
        std.debug.print("i = {}\n", .{i});
        i += 1;
    }
    
    std.debug.print("Using break in for loop:\n", .{});
    for (0..10) |j| {
        if (j == 5) break;
        std.debug.print("j = {}\n", .{j});
    }
}

continue Statement

continue is used to skip the current iteration and continue with the next:

zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Using continue to skip even numbers:\n", .{});
    for (0..10) |i| {
        if (i % 2 == 0) continue;
        std.debug.print("Odd number: {}\n", .{i});
    }
    
    var j: i32 = 0;
    std.debug.print("Using continue in while loop:\n", .{});
    while (j < 10) : (j += 1) {
        if (j % 3 == 0) continue;
        std.debug.print("Not divisible by 3: {}\n", .{j});
    }
}

Nested Loops

Basic Nested Loops

zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Multiplication table (partial):\n", .{});
    for (1..4) |i| {
        for (1..4) |j| {
            const product = i * j;
            std.debug.print("{} × {} = {}\t", .{ i, j, product });
        }
        std.debug.print("\n", .{});
    }
}

Labeled Loop Control

You can use labels to control specific loops:

zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Labeled nested loops:\n", .{});
    
    outer: for (0..3) |i| {
        for (0..3) |j| {
            if (i == 1 and j == 1) {
                std.debug.print("Breaking outer loop at ({}, {})\n", .{ i, j });
                break :outer;
            }
            std.debug.print("({}, {})\n", .{ i, j });
        }
    }
    
    std.debug.print("Outer loop ended\n", .{});
}

Labeled continue

zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Labeled continue:\n", .{});
    
    outer: for (0..3) |i| {
        for (0..3) |j| {
            if (j == 1) {
                std.debug.print("Skipping rest of outer loop (i={})\n", .{i});
                continue :outer;
            }
            std.debug.print("({}, {})\n", .{ i, j });
        }
    }
}

Loop Expressions

while Expression

while loops can be used as expressions that return values:

zig
const std = @import("std");

pub fn main() void {
    var i: i32 = 0;
    
    const result = while (i < 5) : (i += 1) {
        if (i == 3) break i * 10;
    } else 0;
    
    std.debug.print("while expression result: {}\n", .{result});
}

for Expression

for loops can also be used as expressions:

zig
const std = @import("std");

pub fn main() void {
    const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
    const found = for (numbers) |number| {
        if (number == 3) break true;
    } else false;
    
    std.debug.print("Found number 3: {}\n", .{found});
    
    const sum = for (numbers) |number| {
        // for loops can't directly accumulate, this demonstrates syntax
        if (number > 10) break number;
    } else 0;
    
    std.debug.print("First number greater than 10: {}\n", .{sum});
}

Infinite Loops

Using while(true)

zig
const std = @import("std");

pub fn main() void {
    var counter: i32 = 0;
    
    std.debug.print("Infinite loop (will exit when counter reaches 3):\n", .{});
    while (true) {
        std.debug.print("Counter: {}\n", .{counter});
        counter += 1;
        
        if (counter >= 3) break;
    }
}

Loop Optimization

Compile-time Loops

Using comptime allows loops to execute at compile time:

zig
const std = @import("std");

pub fn main() void {
    // Compile-time calculation
    comptime var sum: i32 = 0;
    comptime var i: i32 = 1;
    
    comptime {
        while (i <= 10) : (i += 1) {
            sum += i;
        }
    }
    
    std.debug.print("Sum of 1 to 10 (compile-time): {}\n", .{sum});
    
    // Compile-time array generation
    const squares = comptime blk: {
        var result: [5]i32 = undefined;
        for (result, 0..) |*item, index| {
            item.* = @intCast((index + 1) * (index + 1));
        }
        break :blk result;
    };
    
    std.debug.print("First 5 square numbers: ");
    for (squares) |square| {
        std.debug.print("{} ", .{square});
    }
    std.debug.print("\n");
}

Loop Unrolling

For small fixed-iteration loops, the compiler may automatically unroll them:

zig
const std = @import("std");

pub fn main() void {
    const data = [_]i32{ 1, 2, 3, 4, 5 };
    var sum: i32 = 0;
    
    // Small loops may be unrolled by the compiler
    for (data) |value| {
        sum += value;
    }
    
    std.debug.print("Array sum: {}\n", .{sum});
}

Practical Application Examples

Search Algorithm

zig
const std = @import("std");

fn findElement(array: []const i32, target: i32) ?usize {
    for (array, 0..) |element, index| {
        if (element == target) {
            return index;
        }
    }
    return null;
}

pub fn main() void {
    const numbers = [_]i32{ 10, 20, 30, 40, 50 };
    const target = 30;
    
    if (findElement(&numbers, target)) |index| {
        std.debug.print("Found {} at index {}\n", .{ target, index });
    } else {
        std.debug.print("Did not find {}\n", .{target});
    }
}

Array Processing

zig
const std = @import("std");

fn processArray(array: []i32) void {
    // Multiply all even numbers by 2
    for (array) |*element| {
        if (element.* % 2 == 0) {
            element.* *= 2;
        }
    }
}

pub fn main() void {
    var numbers = [_]i32{ 1, 2, 3, 4, 5, 6 };
    
    std.debug.print("Before processing: ");
    for (numbers) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");
    
    processArray(&numbers);
    
    std.debug.print("After processing: ");
    for (numbers) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");
}

String Processing

zig
const std = @import("std");

fn countVowels(text: []const u8) u32 {
    var count: u32 = 0;
    const vowels = "aeiouAEIOU";
    
    for (text) |char| {
        for (vowels) |vowel| {
            if (char == vowel) {
                count += 1;
                break;
            }
        }
    }
    
    return count;
}

pub fn main() void {
    const text = "Hello, World!";
    const vowel_count = countVowels(text);
    
    std.debug.print("Text \"{s}\" has {} vowels\n", .{ text, vowel_count });
}

Loop Best Practices

1. Choose the Right Loop Type

zig
const std = @import("std");

pub fn main() void {
    const data = [_]i32{ 1, 2, 3, 4, 5 };
    
    // ✅ Use for when iterating over known collections
    std.debug.print("Using for to iterate array: ");
    for (data) |value| {
        std.debug.print("{} ", .{value});
    }
    std.debug.print("\n");
    
    // ✅ Use while for conditional loops
    var i: i32 = 0;
    std.debug.print("Using while for conditional loop: ");
    while (i < 5) : (i += 1) {
        std.debug.print("{} ", .{i});
    }
    std.debug.print("\n");
}

2. Avoid Infinite Loops

zig
const std = @import("std");

pub fn main() void {
    var attempts: i32 = 0;
    const max_attempts = 10;
    
    // ✅ Good practice: clear exit condition
    while (attempts < max_attempts) : (attempts += 1) {
        // Simulate some operation
        if (attempts == 3) {
            std.debug.print("Operation successful, exiting loop\n", .{});
            break;
        }
        std.debug.print("Attempt {}\n", .{attempts + 1});
    }
    
    if (attempts >= max_attempts) {
        std.debug.print("Reached maximum attempts\n", .{});
    }
}

3. Use Loop Control Statements Appropriately

zig
const std = @import("std");

pub fn main() void {
    const numbers = [_]i32{ 1, -2, 3, -4, 5, 0, 6 };
    var positive_sum: i32 = 0;
    
    for (numbers) |number| {
        // Skip negative numbers
        if (number < 0) continue;
        
        // Stop when encountering 0
        if (number == 0) break;
        
        positive_sum += number;
    }
    
    std.debug.print("Sum of positive numbers: {}\n", .{positive_sum});
}

Practice Exercises

Exercise 1: Number Processing

zig
const std = @import("std");

pub fn main() void {
    // TODO: Use loops to complete the following tasks:
    // 1. Calculate the sum of 1 to 100
    // 2. Find all prime numbers from 1 to 50
    // 3. Calculate the first 10 terms of the Fibonacci sequence
}

Exercise 2: Array Operations

zig
const std = @import("std");

pub fn main() void {
    var numbers = [_]i32{ 64, 34, 25, 12, 22, 11, 90 };
    
    // TODO: Implement bubble sort algorithm
    // TODO: Find the maximum and minimum values in the array
    // TODO: Calculate the average of the array
}

Exercise 3: Pattern Matching

zig
const std = @import("std");

pub fn main() void {
    const text = "Hello, World! This is a test.";
    
    // TODO: Count the occurrences of each character
    // TODO: Find the longest word
    // TODO: Reverse each word in the string
}

Summary

This chapter covered loop structures in Zig in detail:

  • while loops: conditional loops with continue expression support
  • for loops: iterating over arrays, slices, and other iterable objects
  • ✅ Loop control: break and continue statements
  • ✅ Nested loops and labeled control
  • ✅ Loop expressions and return values
  • ✅ Compile-time loops and optimization
  • ✅ Practical applications and best practices

Mastering loops is a fundamental programming skill. They are essential tools for implementing repetitive operations and data processing. In the next chapter, we'll learn about Zig's control flow statements.

Content is for learning and research only.