Skip to content

Array Literals

An array literal is the syntax you use to write array values directly in source code.

An array literal is the syntax you use to write array values directly in source code.

You already saw this form:

const numbers = [3]i32{ 10, 20, 30 };

This creates a fixed array with 3 elements. Each element has type i32.

The full shape is:

[3]i32{ 10, 20, 30 }

Read it as:

an array of 3 i32 values, initialized with 10, 20, and 30

The Parts of an Array Literal

An array literal has two main parts:

[3]i32{ 10, 20, 30 }

The part before the braces is the array type:

[3]i32

The part inside the braces is the data:

{ 10, 20, 30 }

So this:

const numbers: [3]i32 = [3]i32{ 10, 20, 30 };

means:

create a value of type [3]i32 and fill it with these 3 values

Usually, you do not need to write the type twice.

const numbers = [3]i32{ 10, 20, 30 };

This is enough because Zig can infer the variable type from the literal.

Letting Zig Count the Length

You can use _ to ask Zig to count the number of elements:

const numbers = [_]i32{ 10, 20, 30 };

This has the same type as:

const numbers = [3]i32{ 10, 20, 30 };

The underscore does not mean “unknown forever.” It means “compiler, calculate this for me.”

After compilation, the type is still fixed:

[3]i32

This is common Zig style when the literal is written directly in the code.

const vowels = [_]u8{ 'a', 'e', 'i', 'o', 'u' };

The compiler sees 5 values, so the type is:

[5]u8

Empty Arrays

An array can have length zero.

const empty = [_]u8{};

This creates an array with no elements.

Its type is:

[0]u8

Zero-length arrays are not common in beginner code, but they are valid. They can appear in generic code, compile-time code, tests, and APIs where “no items” is a valid case.

All Elements Must Match the Element Type

In this array:

const numbers = [_]i32{ 10, 20, 30 };

each element must be usable as an i32.

This works:

const numbers = [_]i32{ 1, 2, 3 };

This does not work:

const numbers = [_]i32{ 1, 2, "three" };

The string "three" is not an i32.

Zig does not silently guess a mixed array type. A fixed array has one element type, and every element must fit that type.

Array Literals with Explicit Variable Types

Sometimes the variable already has a type.

const numbers: [3]i32 = .{ 10, 20, 30 };

Here, the literal starts with a dot:

.{ 10, 20, 30 }

This means: use the type expected from the left side.

The left side says:

const numbers: [3]i32

So Zig knows the literal must be a [3]i32.

This form is very common in Zig.

const rgb: [3]u8 = .{ 255, 128, 0 };

The type is written once, on the variable. The initializer uses .{ ... }.

Why .{ ... } Exists

The dot literal is useful because Zig often knows the expected type from context.

For example:

fn printColor(color: [3]u8) void {
    _ = color;
}

pub fn main() void {
    printColor(.{ 255, 128, 0 });
}

The function expects [3]u8, so Zig can understand:

.{ 255, 128, 0 }

as:

[3]u8{ 255, 128, 0 }

This keeps code shorter without making it unclear.

Array Literals and Mutability

The literal itself is just a value. Mutability depends on the variable you assign it to.

This is immutable:

const numbers = [_]i32{ 10, 20, 30 };

You cannot change its elements.

numbers[0] = 99; // error

This is mutable:

var numbers = [_]i32{ 10, 20, 30 };
numbers[0] = 99;

The array now contains:

99, 20, 30

The literal syntax does not decide mutability. const or var decides mutability.

Repeating Values

Zig can build an array by repeating a value.

const zeroes = [_]u8{0} ** 8;

This creates:

0, 0, 0, 0, 0, 0, 0, 0

Its type is:

[8]u8

This is useful for buffers:

var buffer = [_]u8{0} ** 1024;

This creates 1024 bytes, all initialized to zero.

You can repeat other values too:

const ones = [_]i32{1} ** 5;

This creates:

1, 1, 1, 1, 1

Concatenating Array Literals

Zig can concatenate arrays using ++.

const a = [_]u8{ 1, 2 };
const b = [_]u8{ 3, 4 };

const c = a ++ b;

The result is:

1, 2, 3, 4

The type of c is:

[4]u8

This also works directly with literals:

const digits = [_]u8{ '0', '1', '2' } ++ [_]u8{ '3', '4', '5' };

The result is a single array:

[6]u8

Array concatenation happens at compile time when the arrays are known at compile time.

Repetition and Concatenation Together

You can combine ** and ++.

const border = [_]u8{'='} ** 10 ++ [_]u8{'\n'};

This creates:

========== newline

More precisely, it creates 11 bytes: ten = bytes and one newline byte.

This style is useful for constant data.

const header =
    [_]u8{'-'} ** 20 ++
    [_]u8{'\n'};

For large or dynamic strings, you will normally use other tools. But for fixed compile-time data, array literals are simple and efficient.

Nested Array Literals

Arrays can contain arrays.

const matrix = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};

This has type:

[2][3]i32

Read this as:

an array of 2 rows, where each row is an array of 3 i32 values

Each row can use dot literal syntax because Zig already knows each row must be [3]i32.

This is equivalent to:

const matrix = [2][3]i32{
    [3]i32{ 1, 2, 3 },
    [3]i32{ 4, 5, 6 },
};

The shorter version is usually better:

const matrix = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};

Arrays of Struct Values

Array literals can contain structs.

const Point = struct {
    x: i32,
    y: i32,
};

const points = [_]Point{
    .{ .x = 0, .y = 0 },
    .{ .x = 10, .y = 20 },
    .{ .x = -5, .y = 3 },
};

The type of points is:

[3]Point

Each element is a Point.

This is a common pattern for lookup tables, test cases, coordinates, tokens, and configuration data.

Arrays of Anonymous Structs

Zig can also infer an anonymous struct type in some contexts.

const items = .{
    .{ .name = "apple", .price = 100 },
    .{ .name = "banana", .price = 80 },
};

This does not create a normal fixed array in the same simple way as [N]T. It creates a tuple-like value where the fields are known at compile time.

For beginners, prefer explicit array types when you want an array:

const Item = struct {
    name: []const u8,
    price: u32,
};

const items = [_]Item{
    .{ .name = "apple", .price = 100 },
    .{ .name = "banana", .price = 80 },
};

This is clearer and easier to pass to functions.

Array Literals and Strings

String literals are closely related to arrays of bytes.

const name = "zig";

This stores the bytes for:

z i g

You can also write those bytes manually:

const name_bytes = [_]u8{ 'z', 'i', 'g' };

But these are not exactly the same type. String literals in Zig have sentinel information, which we will cover later.

For now, the important point is simple: text in Zig is stored as bytes, and array literals are one way to write bytes directly.

Common Mistake: Wrong Length

This is an error:

const numbers = [2]i32{ 10, 20, 30 };

The type says 2 elements, but the literal gives 3.

This is also an error:

const numbers = [4]i32{ 10, 20, 30 };

The type says 4 elements, but the literal gives 3.

The length and the number of values must match.

Use _ when you want the compiler to count:

const numbers = [_]i32{ 10, 20, 30 };

Common Mistake: Confusing Arrays and Slices

This is an array:

const numbers = [_]i32{ 10, 20, 30 };

Its type is:

[3]i32

This is a slice:

const slice = numbers[0..];

Its type is:

[]const i32

An array owns the elements. A slice points to elements stored somewhere else.

Array literals create arrays, not slices. You can create a slice from an array after the array exists.

Common Mistake: Forgetting the Dot Literal Needs Context

This works:

const numbers: [3]i32 = .{ 10, 20, 30 };

The type is known from the left side.

This may not work in places where Zig has no expected type:

const numbers = .{ 10, 20, 30 };

This creates a tuple-like value, not a plain [3]i32.

For a real array, write:

const numbers = [_]i32{ 10, 20, 30 };

or:

const numbers: [3]i32 = .{ 10, 20, 30 };

A Complete Example

const std = @import("std");

pub fn main() void {
    const primes = [_]u32{ 2, 3, 5, 7, 11 };
    const zeroes = [_]u8{0} ** 4;
    const more_primes = primes ++ [_]u32{ 13, 17 };

    std.debug.print("primes:\n", .{});
    for (primes, 0..) |value, index| {
        std.debug.print("  primes[{}] = {}\n", .{ index, value });
    }

    std.debug.print("zeroes length = {}\n", .{zeroes.len});

    std.debug.print("more_primes:\n", .{});
    for (more_primes, 0..) |value, index| {
        std.debug.print("  more_primes[{}] = {}\n", .{ index, value });
    }
}

This program shows three array literal patterns:

const primes = [_]u32{ 2, 3, 5, 7, 11 };

A normal array literal with inferred length.

const zeroes = [_]u8{0} ** 4;

A repeated array literal.

const more_primes = primes ++ [_]u32{ 13, 17 };

A concatenated array.

Summary

An array literal writes array data directly in your code.

The most common forms are:

const a = [3]i32{ 1, 2, 3 };
const b = [_]i32{ 1, 2, 3 };
const c: [3]i32 = .{ 1, 2, 3 };

Use [N]T{ ... } when you want to state the length directly.

Use [_]T{ ... } when you want Zig to count the elements.

Use .{ ... } when the expected type is already known.

Array literals are small, direct, and precise. They are one of the first places where Zig’s type system becomes visible: the number of elements and the type of each element are checked before the program runs.