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:
123This 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}); // bugThe 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 undefinedThe 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:
helloThe 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:
| Situation | Reason |
|---|---|
| A buffer will be filled immediately | avoids unnecessary initialization |
| An API writes into caller-provided memory | storage exists before value is written |
| You are building a value step by step | fields or elements are assigned later |
| Performance matters and initialization would be wasted | avoids extra work |
Avoid undefined when:
| Situation | Better choice |
|---|---|
| You already know the value | initialize directly |
| You mean zero | use 0 or zero-filled data |
| You mean no value | use null with an optional |
| You are unsure | do 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.