Zig Arrays
An array is a fixed-size collection of values of the same type stored in a continuous block of memory. The size of an array is set at compile time and never changes. When you need a list of numbers, strings, or any values where the count is known in advance, arrays are the right tool.
Declaring an Array
const temps: [5]f32 = [5]f32{ 22.5, 19.0, 25.3, 18.7, 23.1 };
The type [5]f32 means: an array of exactly 5 elements, each a 32-bit float. You can let Zig count the elements using the _ size infer:
const temps = [_]f32{ 22.5, 19.0, 25.3, 18.7, 23.1 };
// ^^^
// Zig counts = 5
Memory layout:
Index: [0] [1] [2] [3] [4]
Value: [22.5] [19.0] [25.3] [18.7] [23.1]
|------ 5 × 4 bytes = 20 bytes total -------|
Accessing Elements
const colors = [_][]const u8{ "Red", "Green", "Blue" };
std.debug.print("First: {s}\n", .{colors[0]});
std.debug.print("Second: {s}\n", .{colors[1]});
std.debug.print("Third: {s}\n", .{colors[2]});
colors:
Index: 0 1 2
["Red"] ["Green"] ["Blue"]
↑
colors[0] = "Red"
Arrays in Zig start at index 0. The last valid index is always length - 1. Accessing an index outside this range causes a runtime panic in debug builds — Zig checks bounds automatically.
Modifying Array Elements
Arrays declared with var can be modified. Arrays declared with const cannot:
var scores = [_]u32{ 80, 90, 70 };
scores[1] = 95; // Change second element from 90 to 95
std.debug.print("{d}\n", .{scores[1]}); // 95
Array Length
const primes = [_]u32{ 2, 3, 5, 7, 11, 13 };
const count = primes.len;
std.debug.print("Number of primes: {d}\n", .{count}); // 6
The .len property gives the number of elements. Because the array size is fixed at compile time, .len is a compile-time constant — the compiler knows it without running the program.
Iterating Over an Array
const days = [_][]const u8{
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
};
for (days, 0..) |day, i| {
std.debug.print("Day {d}: {s}\n", .{i + 1, day});
}
i=0, day="Mon" → "Day 1: Mon" i=1, day="Tue" → "Day 2: Tue" ... i=6, day="Sun" → "Day 7: Sun"
Multi-Dimensional Arrays
An array of arrays creates a grid structure — useful for tables, matrices, and game boards:
const grid = [3][3]u8{
[_]u8{ 1, 2, 3 },
[_]u8{ 4, 5, 6 },
[_]u8{ 7, 8, 9 },
};
Visual grid: [0][0]=1 [0][1]=2 [0][2]=3 [1][0]=4 [1][1]=5 [1][2]=6 [2][0]=7 [2][1]=8 [2][2]=9 Access center element: grid[1][1] = 5
for (grid) |row| {
for (row) |cell| {
std.debug.print("{d} ", .{cell});
}
std.debug.print("\n", .{});
}
Output:
1 2 3 4 5 6 7 8 9
Array Initialization Patterns
All Elements the Same Value
// Initialize all 10 elements to zero
const zeroes = [_]u32{0} ** 10;
// Initialize all 5 elements to 255
const maxed = [_]u8{255} ** 5;
zeroes: [ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 ] maxed: [255|255|255|255|255]
The ** operator repeats the element the specified number of times. This is a compile-time operation — no runtime loop needed.
Undefined — Reserve Space Without Initializing
var buffer: [100]u8 = undefined; // Fill later... buffer[0] = 'H'; buffer[1] = 'i';
Concatenating Arrays at Compile Time
const a = [_]u32{ 1, 2, 3 };
const b = [_]u32{ 4, 5, 6 };
const combined = a ++ b;
// combined = [1, 2, 3, 4, 5, 6]
The ++ operator joins two arrays into a new one at compile time. Both arrays must contain elements of the same type. The result is a new array with a length equal to the sum of both.
Arrays vs Slices
Array: Slice:
+--------------------------+ +---------------------------+
| Fixed size known at | | Flexible — points to part |
| compile time | | of an array |
| [5]u32 | | []u32 |
| Owns its memory | | Borrows memory |
| Cannot resize | | Length stored separately |
+--------------------------+ +---------------------------+
const arr: [5]u32 = .{1,2,3,4,5};
const slc: []const u32 = arr[1..4]; // elements 1, 2, 3
Passing Arrays to Functions
fn total(data: []const u32) u32 {
var sum: u32 = 0;
for (data) |val| sum += val;
return sum;
}
const nums = [_]u32{ 10, 20, 30, 40 };
const t = total(&nums);
std.debug.print("Total: {d}\n", .{t}); // 100
Passing &nums (the address of the array) converts it to a slice automatically in this context. Functions that accept []const u32 work with arrays of any length — much more flexible than typing the exact size into the function signature.
Practical Example: Class Grade Average
const std = @import("std");
pub fn main() void {
const grades = [_]f32{ 88.0, 92.5, 75.0, 96.0, 83.5, 79.0 };
var total: f32 = 0;
for (grades) |g| total += g;
const avg = total / @as(f32, @floatFromInt(grades.len));
std.debug.print("Class average: {d:.2}\n", .{avg});
}
Output:
Class average: 85.67
