Skip to content

Recursion, loops, and tables

The compiler executes a complete subset of the language: arithmetic, control flow, and method calls on arrays and strings. This means that recursion and loops work normally inside comptime contexts.

Recursion

A @comptime fn can call itself. The compiler evaluates the recursion down to the base case and bakes the result as a literal. The runtime never sees the call stack — only the final number.

Comptime Fibonacci: fib(10) and fib(15) baked directly into the binary.

03-recursion.zolo
Playground
// Feature: comptime recursion — a `@comptime fn` calling itself
// Syntax: same as above; recursion is fine, but you must terminate.
// When to use: classic precomputed sequences (Fibonacci, factorials,
// CRC tables) — the runtime sees only the final number.

@comptime
fn fib(n: int) -> int {
    if n <= 1 { return n }
    return fib(n - 1) + fib(n - 2)
}

let f10 = comptime fib(10)
print(f10)
// expected: 55

let f15 = comptime fib(15)
print(f15)
// expected: 610

Loops

A for inside comptime { ... } runs entirely in the compiler. Gauss's sum from 1 to 100 is a classic example: the answer is always 5050 and there is no reason to compute it at runtime.

Comptime loop: sums 1..=100 in the compiler, the program receives the literal 5050.

04-loop-precompute.zolo
Playground
// Feature: comptime loops — a `for` inside a comptime block

// Syntax: any control flow is fair game; the block runs in the

// compiler. The tail expression is the value baked in.

// When to use: precomputed sums, lookup tables, anything where the

// answer is known at build time and you want the binary to ship a

// literal instead of a loop.


// Gauss sum 1..=100, computed at build time.

let total = comptime {
    var s = 0
    for i in 1..=100 {
        s = s + i
    }
    s
}

print(total)
// expected: 5050

Lookup tables

The same pattern serves to generate literal arrays — tables of squares, primes, CRC values, palettes. Build the array inside the block, end it as a tail expression, and the compiler emits [1, 4, 9, 16, 25, ...] directly into the binary.

Array of squares from 1 to 5 generated at compile time.

05-array-table.zolo
Playground
// Feature: comptime arrays — emit a literal `[v1, v2, ...]`

// Syntax: build the array inside `comptime { ... }`, end with the

// array as the tail expression. The runtime sees a baked literal.

// When to use: lookup tables (squares, primes, lookup-by-index

// for hot paths), CRC tables, palette tables.


let squares = comptime {
    var out = []
    for n in 1..=5 {
        out = out.push(n * n)
    }
    out
}

print(squares[0])  // expected: 1

print(squares[1])  // expected: 4

print(squares[2])  // expected: 9

print(squares[3])  // expected: 16

print(squares[4])  // expected: 25

Challenge

Adapt the table example to generate the first 10 multiples of 7 and access squares[9] to confirm the result.

enespt-br