Skip to content

Undefined Values

In Zig, undefined means “this value has not been initialized.”

In Zig, undefined means “this value has not been initialized.”

It is not zero.

It is not null.

It is not an empty value.

It means the memory exists, but its contents are not meaningful yet.

var x: i32 = undefined;

This creates a variable named x with type i32, but it does not give x a real value.

Why undefined Exists

Sometimes you need to reserve space first and fill it later.

For example:

var buffer: [1024]u8 = undefined;

This creates an array of 1024 bytes. Zig does not spend time filling the array with zeros. That can be useful for performance.

Later, you should write real data into it:

buffer[0] = 65;
buffer[1] = 66;
buffer[2] = 67;

Now the first three bytes have meaningful values.

Reading Undefined Values Is a Bug

This is wrong:

const std = @import("std");

pub fn main() void {
    var x: i32 = undefined;

    std.debug.print("{}\n", .{x});
}

The variable x has not been initialized. Reading it is invalid program behavior.

The value might look random. It might appear to work once. It might fail later. You cannot rely on it.

undefined is a promise from you to the compiler: “I will write a valid value before reading this.”

If you break that promise, the program is wrong.

Prefer Normal Initialization

For beginner code, prefer this:

const x: i32 = 42;

or:

var count: usize = 0;

Instead of this:

var count: usize = undefined;
count = 0;

The first version is clearer and safer.

Use undefined only when you have a real reason.

Common Use: Buffers

A common valid use is temporary storage.

const std = @import("std");

pub fn main() void {
    var buffer: [64]u8 = undefined;

    const message = "hello";

    @memcpy(buffer[0..message.len], message);

    std.debug.print("{s}\n", .{buffer[0..message.len]});
}

Here, the full buffer starts as undefined. That is acceptable because we only read the part we filled.

This line writes into the first part of the buffer:

@memcpy(buffer[0..message.len], message);

This line reads only the initialized part:

buffer[0..message.len]

The rest of the buffer is still undefined, but we do not read it.

Common Use: Output Parameters

Some APIs fill memory that you provide.

A simplified example:

fn fillNumber(out: *i32) void {
    out.* = 123;
}

The caller can create storage first:

var value: i32 = undefined;
fillNumber(&value);

After the function call, value has been initialized.

const std = @import("std");

fn fillNumber(out: *i32) void {
    out.* = 123;
}

pub fn main() void {
    var value: i32 = undefined;

    fillNumber(&value);

    std.debug.print("{}\n", .{value});
}

Output:

123

This pattern is useful when a function needs to write into caller-owned memory.

undefined with Arrays

Arrays are often initialized with undefined when they will be filled later.

var numbers: [3]i32 = undefined;

numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

Now all elements have real values.

This is safe:

const std = @import("std");

pub fn main() void {
    var numbers: [3]i32 = undefined;

    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;

    std.debug.print("{} {} {}\n", .{
        numbers[0],
        numbers[1],
        numbers[2],
    });
}

This is not safe:

const std = @import("std");

pub fn main() void {
    var numbers: [3]i32 = undefined;

    numbers[0] = 10;

    std.debug.print("{}\n", .{numbers[1]}); // bug
}

Only numbers[0] was initialized. Reading numbers[1] is wrong.

undefined with Structs

You can also use undefined with structs.

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

var p: Point = undefined;

But you must initialize fields before reading them.

p.x = 10;
p.y = 20;

Complete example:

const std = @import("std");

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

pub fn main() void {
    var p: Point = undefined;

    p.x = 10;
    p.y = 20;

    std.debug.print("({}, {})\n", .{ p.x, p.y });
}

Output:

(10, 20)

This is wrong:

var p: Point = undefined;

p.x = 10;

std.debug.print("{}\n", .{p.y}); // bug

The field p.y has not been initialized.

Partial Initialization Requires Discipline

undefined lets you initialize data step by step.

That power is useful, but it requires care.

This is fine:

var pair: [2]u8 = undefined;
pair[0] = 1;
pair[1] = 2;

This is dangerous:

var pair: [2]u8 = undefined;
pair[0] = 1;

// pair[1] is still undefined

The compiler cannot always prove whether every part has been initialized before use. You are responsible for using undefined correctly.

undefined Is Different from Zero

This is initialized:

var x: i32 = 0;

This is not:

var x: i32 = undefined;

Zero is a real value. Undefined is not.

This array is filled with zero bytes:

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

This array is not filled with meaningful bytes:

var buffer: [64]u8 = undefined;

Use zero initialization when you need zeros.

Use undefined when you will overwrite the data before reading it.

undefined Is Different from null

null means “no value” for optional types.

var maybe_number: ?i32 = null;

This variable has a meaningful value: it contains no integer right now.

undefined means there is no meaningful value at all.

var maybe_number: ?i32 = undefined;

This does not mean null. It means the optional itself has not been initialized.

That distinction matters.

Prefer this when you mean “empty”:

var maybe_number: ?i32 = null;

Use this only when you will assign a real optional value before reading it:

var maybe_number: ?i32 = undefined;

Debug Builds May Help, But Do Not Rely on It

In debug-oriented builds, Zig may fill undefined memory with recognizable patterns to help catch bugs. That can make errors easier to notice.

But you should not depend on any particular value.

This is still wrong:

var x: i32 = undefined;
std.debug.print("{}\n", .{x});

The rule is simple: never read undefined.

A Complete Example: Filling a Buffer

const std = @import("std");

fn writeGreeting(buffer: []u8) []u8 {
    const text = "hello";

    @memcpy(buffer[0..text.len], text);

    return buffer[0..text.len];
}

pub fn main() void {
    var storage: [64]u8 = undefined;

    const greeting = writeGreeting(&storage);

    std.debug.print("{s}\n", .{greeting});
}

Output:

hello

The storage starts undefined:

var storage: [64]u8 = undefined;

The function writes into part of it:

@memcpy(buffer[0..text.len], text);

The program reads only the initialized part:

const greeting = writeGreeting(&storage);

That is the correct pattern.

When to Use undefined

Use undefined when:

SituationReason
A buffer will be filled immediatelyavoids unnecessary initialization
An API writes into caller-provided memorystorage exists before value is written
You are building a value step by stepfields or elements are assigned later
Performance matters and initialization would be wastedavoids extra work

Avoid undefined when:

SituationBetter choice
You already know the valueinitialize directly
You mean zerouse 0 or zero-filled data
You mean no valueuse null with an optional
You are unsuredo not use undefined

The Main Idea

undefined is not a value you use. It is a way to create storage without initializing it.

It can make code faster and more flexible, especially for buffers and low-level APIs. But it also removes a safety net.

The discipline is simple: write before read.