Zig Interview Questions with Answers
Most Asked Zig Interview Questions for Software Engineer Roles
Introduction
This page provides a complete collection of Zig Interview Questions and Answers designed for software engineers, systems programmers, C/C++ developers, and anyone preparing for technical interviews focused on modern low-level programming. Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software. It offers manual memory management, compile-time code execution, and seamless C interoperability – all without hidden allocations or a preprocessor. This interview guide covers beginner, intermediate, and advanced Zig concepts including syntax, memory management, comptime, error handling, generics, concurrency, FFI, build system, testing, and real-world coding patterns.
Why Zig?
- No hidden allocations – explicit control over memory, predictable performance
- Comptime metaprogramming – code generation and type introspection at compile time
- Seamless C integration – call C libraries directly without bindings or overhead
- Cross-compilation out of the box – build for any target from one machine
- Safety by default – undefined behavior checks, optional runtime bounds checking
- Growing adoption – used in game development, embedded systems, and systems programming
Most Asked Zig Interview Questions
Zig is a general-purpose programming language designed for robustness, optimality, and maintainability. It offers manual memory management, compile-time code execution, and seamless C interoperability.
- No hidden allocations – explicit control
- Comptime – powerful metaprogramming
- No preprocessor – compile-time execution
- Cross-compilation – built-in support
- Safety – undefined behavior checks
// Hello World in Zig
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello, World!\n", .{});
}Variables in Zig use var for mutable and const for immutable. Types can be inferred or explicitly specified.
- var: mutable
- const: immutable
- Type inference:
const x = 10 - Explicit type:
var y: i32 = 20
// Variables in Zig
const std = @import("std");
pub fn main() !void {
// Mutable variable
var x: i32 = 10;
x = 20;
// Immutable variable
const y: i32 = 30;
// Type inference
const z = 40;
// Variable with explicit type
var name: []const u8 = "Alice";
std.debug.print("{d} {d} {d} {s}\n", .{ x, y, z, name });
}Zig provides signed/unsigned integers, floats, booleans, strings (slices), arrays, and compound types like structs and unions.
- Integers: i8, u16, i32, u64, etc.
- Floats: f32, f64
- Boolean: bool
- String:
[]const u8 - Array:
[N]T - Slice:
[]T
// Data Types in Zig
const std = @import("std");
pub fn main() !void {
// Integers
const a: i8 = 10; // signed 8-bit
const b: u16 = 200; // unsigned 16-bit
const c: i32 = -1000; // signed 32-bit
const d: u64 = 100000; // unsigned 64-bit
// Floats
const e: f32 = 3.14;
const f: f64 = 2.718;
// Boolean
const g: bool = true;
// Character (UTF-8 code point)
const h: u8 = 'A';
// String (slice of bytes)
const s: []const u8 = "Hello Zig";
// Array
const arr: [3]i32 = .{ 1, 2, 3 };
// Slice
const slice: []const i32 = &arr;
std.debug.print("{d} {d} {d} {d}\n", .{ a, b, c, d });
}Functions are defined with the fn keyword. They can return values, have parameters, and support multiple return values via structs.
- Syntax:
fn name(params) ReturnType { ... } - Implicit return: expression body
- Multiple returns: anonymous struct
- Higher-order: function pointers
// Functions in Zig
const std = @import("std");
// Function with return value
fn add(a: i32, b: i32) i32 {
return a + b;
}
// Function with implicit return (expression body)
fn subtract(a: i32, b: i32) i32 = a - b;
// Function with multiple return values (tuple)
fn divide(a: i32, b: i32) struct { quotient: i32, remainder: i32 } {
return .{ .quotient = a / b, .remainder = a % b };
}
// Higher-order function
fn apply(a: i32, b: i32, f: *const fn (i32, i32) i32) i32 {
return f(a, b);
}
// Anonymous function (closure)
const multiply = struct {
fn call(a: i32, b: i32) i32 { return a * b; }
}.call;
pub fn main() !void {
const sum = add(5, 3);
const diff = subtract(10, 4);
const res = divide(10, 3);
const product = apply(6, 7, multiply);
std.debug.print("{} {} {} {}\n", .{ sum, diff, res.quotient, product });
}Arrays are fixed-size, slices are dynamically-sized views. Slices are used for most operations.
- Array:
var a: [5]i32 = .{1,2,3,4,5}; - Slice:
const s = a[0..3]; - Modify: arrays can be mutated
- Length:
lenandslice.len
// Arrays and Slices in Zig
const std = @import("std");
pub fn main() !void {
// Array (fixed size)
var numbers: [5]i32 = .{ 1, 2, 3, 4, 5 };
// Modify
numbers[2] = 10;
// Slice (dynamic view)
const slice = numbers[0..3]; // first 3 elements
// Iterate
for (numbers) |num| {
std.debug.print("{}\n", .{num});
}
// Slice operations
const slice2 = numbers[1..4];
const sum = slice2[0] + slice2[1] + slice2[2];
std.debug.print("sum: {}\n", .{sum});
}Zig uses allocators for dynamic memory. Common allocators include page_allocator, GeneralPurposeAllocator, and FixedBufferAllocator.
- Allocator:
allocator.alloc(T, n) - Free:
allocator.free(slice) - Create:
allocator.create(T) - Destroy:
allocator.destroy(ptr)
// Slices and arrays in Zig (continued)
const std = @import("std");
pub fn main() !void {
// Slice from array
var arr: [5]i32 = .{ 1, 2, 3, 4, 5 };
const sl = arr[0..];
// Dynamic allocation (using allocator)
var allocator = std.heap.page_allocator;
// Allocate a slice of 10 integers
const dynamic = try allocator.alloc(i32, 10);
defer allocator.free(dynamic);
// Fill
for (dynamic) |*item, i| {
item.* = @intCast(i32, i * 2);
}
// Print
for (dynamic) |val| {
std.debug.print("{}\n", .{val});
}
}Structs group data and can have methods. They are value types (copied on assignment).
- Definition:
const Person = struct { name: []const u8, age: u8 }; - Instantiation:
var p = Person{ .name = "Alice", .age = 25 }; - Methods:
pub fn greet(self: Person) void { ... } - Default values: can use default initialization
// Structs in Zig
const std = @import("std");
// Define a struct
const Person = struct {
name: []const u8,
age: u8,
city: []const u8,
// Method
pub fn greet(self: Person) void {
std.debug.print("Hello, my name is {s}\n", .{self.name});
}
// Constructor (optional)
pub fn init(name: []const u8, age: u8, city: []const u8) Person {
return .{ .name = name, .age = age, .city = city };
}
};
pub fn main() !void {
var p1 = Person.init("Alice", 25, "NYC");
var p2 = Person{ .name = "Bob", .age = 30, .city = "LA" };
p1.greet();
p2.greet();
// Copy (by value)
var p3 = p1;
p3.age = 26;
std.debug.print("p1.age: {}, p3.age: {}\n", .{ p1.age, p3.age });
}Enums define a set of values; unions can hold one of several types, often with an enum tag for safety.
- Enum:
enum { red, green, blue } - Tagged union:
union(enum) { ... } - Matching:
switchwith union
// Enums in Zig
const std = @import("std");
// Enum
const Color = enum {
red,
green,
blue,
};
// Enum with values
const Status = enum(u8) {
success = 200,
error = 500,
loading = 100,
};
// Union (tagged union)
const Result = union(enum) {
success: []const u8,
error: []const u8,
loading: void,
};
pub fn main() !void {
const c = Color.green;
const s = Status.success;
std.debug.print("Color: {}, Status: {}\n", .{ c, @enumToInt(s) });
var res = Result{ .success = "Data loaded" };
switch (res) {
.success => |data| std.debug.print("Success: {s}\n", .{data}),
.error => |msg| std.debug.print("Error: {s}\n", .{msg}),
.loading => std.debug.print("Loading...\n", .{}),
}
}Optionals represent a value that may be null. They use the syntax ?T and provide safe unwrapping.
- Declaration:
var maybe: ?i32 = null; - Unwrap:
if (maybe) |v| { ... } - Default:
const val = maybe orelse 0;
// Optionals in Zig
const std = @import("std");
pub fn main() !void {
// Optional type
var maybe: ?i32 = null;
maybe = 42;
// Safe unwrapping
if (maybe) |value| {
std.debug.print("Value: {}\n", .{value});
} else {
std.debug.print("No value\n", .{});
}
// Or default
const val = maybe orelse 0;
std.debug.print("Value or default: {}\n", .{val});
// Optional with error union
var maybe_err: anyerror!i32 = error.Failed;
const num = maybe_err catch 0;
std.debug.print("Caught: {}\n", .{num});
}Zig uses error unions (!T) and explicit handling with try, catch, and errdefer.
- Error set:
error{InvalidInput} - Try:
try function()– propagates errors - Catch:
catch |err| { ... } - Errdefer: runs on error unwind
// Error Handling in Zig
const std = @import("std");
fn divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return a / b;
}
pub fn main() !void {
// Try-catch
const result = divide(10, 2) catch |err| {
std.debug.print("Error: {}\n", .{err});
return err;
};
std.debug.print("Result: {}\n", .{result});
// Using try (propagate)
const res2 = try divide(20, 5);
std.debug.print("Result2: {}\n", .{res2});
// Custom error set
const CustomError = error{InvalidInput, OutOfRange};
fn validate(x: i32) CustomError!void {
if (x < 0) return error.InvalidInput;
if (x > 100) return error.OutOfRange;
}
validate(50) catch |err| {
std.debug.print("Validation error: {}\n", .{err});
};
}Zig supports if, while, for, and switch with expression‑based syntax.
- If:
if (cond) { ... } else { ... } - While:
while (cond) : (update) { ... } - For:
for (items) |item| { ... } - Switch:
switch (value) { ... }
// Control Flow in Zig
const std = @import("std");
pub fn main() !void {
// If-else
const age: u8 = 25;
const status = if (age < 18) "Minor" else "Adult";
std.debug.print("Status: {s}\n", .{status});
// If-else with elif
const grade = 'A';
const result = if (grade == 'A') "Excellent"
else if (grade == 'B') "Good"
else if (grade == 'C') "Fair"
else "Needs Improvement";
std.debug.print("Result: {s}\n", .{result});
// For loop
for (0..5) |i| {
std.debug.print("i: {}\n", .{i});
}
// For with step (not built-in, use while)
var i: i32 = 1;
while (i <= 9) : (i += 2) {
std.debug.print("i: {}\n", .{i});
}
// While loop
var count: u8 = 0;
while (count < 5) : (count += 1) {
std.debug.print("count: {}\n", .{count});
}
// Loop with break
var j: u8 = 0;
while (true) {
std.debug.print("j: {}\n", .{j});
j += 1;
if (j == 5) break;
}
}Comptime allows code execution at compile time, enabling metaprogramming, type generation, and performance optimization.
- comptime keyword
- Comptime functions: evaluated at compile time
- Type parameters: generics
- Inline loops:
inline for
// Comptime in Zig
const std = @import("std");
// Compile-time function
fn factorial(comptime n: u8) u8 {
return if (n <= 1) 1 else n * factorial(n - 1);
}
// Compile-time variable
const FIVE_FACT = factorial(5);
// Comptime with types
fn sum(comptime T: type, items: []const T) T {
var total: T = 0;
for (items) |v| total += v;
return total;
}
pub fn main() !void {
const arr = [_]i32{1, 2, 3, 4, 5};
const s = sum(i32, &arr);
std.debug.print("Sum: {}\n", .{s});
const arr2 = [_]f64{1.5, 2.5, 3.5};
const s2 = sum(f64, &arr2);
std.debug.print("Sum2: {}\n", .{s2});
std.debug.print("5! = {}\n", .{FIVE_FACT});
}Allocators are interfaces for memory management. Common ones: std.heap.page_allocator, GeneralPurposeAllocator, FixedBufferAllocator.
- Alloc:
allocator.alloc(T, n) - Free:
allocator.free(slice) - Realloc:
allocator.realloc(slice, new_len) - Create:
allocator.create(T)
// Allocators in Zig
const std = @import("std");
pub fn main() !void {
// Get a general-purpose allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Allocate a single value
const ptr = try allocator.create(i32);
defer allocator.destroy(ptr);
ptr.* = 42;
// Allocate a slice
const slice = try allocator.alloc(i32, 5);
defer allocator.free(slice);
for (slice) |*item, i| {
item.* = @intCast(i32, i * 2);
}
// Resize
const new_slice = try allocator.realloc(slice, 10);
defer allocator.free(new_slice);
std.debug.print("First: {}\n", .{new_slice[0]});
}Pointers in Zig are explicit and safe. *T is a pointer to T, *const T is immutable. Slices are pointer+length.
- Single item:
var x: i32 = 5; const p = &x; - Dereference:
p.* - Const pointer:
*const i32 - Pointer arithmetic: not directly, use slices
// Slices and pointers in Zig
const std = @import("std");
pub fn main() !void {
var arr: [3]i32 = .{ 10, 20, 30 };
var slice: []i32 = &arr; // slice of entire array
// Pointer to element
const ptr = &arr[1];
// Pointer arithmetic (not allowed directly, use slices)
const slice2 = arr[0..2];
// Mutability
var x: i32 = 5;
const p: *i32 = &x; // pointer to mutable
p.* = 10;
const q: *const i32 = &x; // pointer to const
std.debug.print("x: {}, p.*: {}\n", .{ x, p.* });
}Generics are implemented using comptime parameters. Functions and structs can accept type arguments.
- Generic function:
fn max(comptime T: type, a: T, b: T) T - Generic struct:
fn Stack(comptime T: type) type - Usage:
max(i32, 10, 20)
// Generics in Zig
const std = @import("std");
// Generic function
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// Generic struct
fn Stack(comptime T: type) type {
return struct {
items: []T,
allocator: std.mem.Allocator,
len: usize,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) !Self {
return .{
.items = try allocator.alloc(T, 0),
.allocator = allocator,
.len = 0,
};
}
pub fn push(self: *Self, value: T) !void {
const new_items = try self.allocator.realloc(self.items, self.len + 1);
new_items[self.len] = value;
self.items = new_items;
self.len += 1;
}
pub fn pop(self: *Self) ?T {
if (self.len == 0) return null;
self.len -= 1;
const val = self.items[self.len];
self.items = self.allocator.realloc(self.items, self.len) catch @panic("realloc failed");
return val;
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.items);
}
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var stack = try Stack(i32).init(alloc);
defer stack.deinit();
try stack.push(10);
try stack.push(20);
try stack.push(30);
while (stack.pop()) |val| {
std.debug.print("{} ", .{val});
}
}Zig uses @import for modules. The build system uses build.zig for configuration and dependencies.
- Import:
const std = @import("std"); - Build.zig: defines executable, library, steps
- Dependencies: managed via build.zig
// Build system and imports
// In Zig, import other files with @import("file.zig")
const std = @import("std");
const my_module = @import("my_module.zig");
// Build.zig example (not shown as code snippet)
pub fn main() !void {
std.debug.print("Using import\n", .{});
}Zig has built-in testing with the test keyword. Tests are compiled and run with zig test.
- Test declaration:
test "description" { ... } - Assertions:
std.testing.expect,expectEqual - Error testing:
expectError
// Testing in Zig
const std = @import("std");
test "basic addition" {
const result = add(2, 3);
try std.testing.expectEqual(result, 5);
}
test "division by zero" {
try std.testing.expectError(error.DivisionByZero, divide(10, 0));
}
fn add(a: i32, b: i32) i32 { return a + b; }
fn divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return a / b;
}Comptime reflection allows inspecting types, fields, and performing operations at compile time using std.meta.
- std.meta.fields: list of struct fields
- @typeInfo: get type info
- Comptime loops: iterate over fields
// Comptime reflection
const std = @import("std");
pub fn main() !void {
const S = struct {
x: i32,
y: f64,
name: []const u8,
};
// Iterate over fields at compile time
comptime var field_info = std.meta.fields(S);
inline for (field_info) |field| {
std.debug.print("Field: {s}, type: {}\n", .{ field.name, field.type });
}
}Zig supports async functions with async and await, but requires specific build flags and is not yet stable.
- Async function:
async fn fetch() ![]const u8 - Await:
const result = await frame; - Event loop:
std.event.Loop
// Async/Await in Zig (limited, using async/await)
// Zig has async functions, but they require specific build flags.
// Example:
const std = @import("std");
async fn fetchData() ![]const u8 {
// Simulate async work
return "Data loaded";
}
pub fn main() !void {
var frame = async fetchData();
const result = await frame;
std.debug.print("{s}\n", .{result});
}Zig provides bitwise operators: &, |, ^, ~, <<, >>.
- And:
a & b - Or:
a | b - Xor:
a ^ b - Not:
~a - Shift:
a << 1
// Bit manipulation
const std = @import("std");
pub fn main() !void {
const a: u8 = 0b1010;
const b: u8 = 0b1100;
const and = a & b;
const or = a | b;
const xor = a ^ b;
const not = ~a;
const shl = a << 1;
const shr = a >> 1;
std.debug.print("and: {b}, or: {b}, xor: {b}, not: {b}, shl: {b}, shr: {b}\n", .{ and, or, xor, not, shl, shr });
}Error sets define possible errors. They can be combined and converted implicitly.
- Definition:
const MyError = error{InvalidInput, OutOfRange}; - Usage:
fn process() MyError!i32 - Error union:
!T
// Error Sets in Zig
const std = @import("std");
const MyError = error{
InvalidInput,
OutOfRange,
};
fn process(x: i32) MyError!i32 {
if (x < 0) return error.InvalidInput;
if (x > 100) return error.OutOfRange;
return x * 2;
}
pub fn main() !void {
const result = process(50) catch |err| {
std.debug.print("Error: {}\n", .{err});
return err;
};
std.debug.print("Result: {}\n", .{result});
}You can implement your own allocator by satisfying the std.mem.Allocator interface.
- FixedBufferAllocator: stack-based
- GeneralPurposeAllocator: heap with safety
- Custom: implement
allocFn,resizeFn,freeFn
// Custom allocators
const std = @import("std");
pub fn main() !void {
// Fixed buffer allocator (stack-based)
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
const slice = try allocator.alloc(u8, 10);
defer allocator.free(slice);
for (slice) |*b, i| {
b.* = @intCast(u8, i + 65); // 'A' to 'J'
}
std.debug.print("Slice: {s}\n", .{slice});
}Zig can call C functions using @cImport and @cInclude.
- Import:
const c = @cImport({ @cInclude("stdio.h"); }); - Call:
c.printf("Hello\\n"); - Export:
export fn my_func() void { ... }
// C Interop (FFI)
// Calling C functions
const std = @import("std");
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() !void {
_ = c.printf("Hello from C
");
}Zig supports packed struct for bit-level control of memory layout.
- Definition:
const Flags = packed struct { enabled: bool, mode: u2 }; - Usage: bit-level packing
- Alignment: can specify with
align
// Packed structs and bitfields
const std = @import("std");
const Flags = packed struct {
enabled: bool,
active: bool,
mode: u2, // 2 bits
};
pub fn main() !void {
var flags = Flags{
.enabled = true,
.active = false,
.mode = 2,
};
std.debug.print("enabled: {}, mode: {}\n", .{ flags.enabled, flags.mode });
}inline for unrolls loops at compile time, useful for comptime code.
- Syntax:
inline for (arr) |val| { ... } - Compile-time unrolling
- Limitation: array length must be known at comptime
// Zig's inline loops
const std = @import("std");
pub fn main() !void {
const arr = [_]i32{ 1, 2, 3, 4, 5 };
// Inline for (unrolls at compile time)
inline for (arr) |val| {
std.debug.print("{} ", .{val});
}
std.debug.print("\n", .{});
}defer runs on scope exit, errdefer runs only on error path.
- defer: cleanup always
- errdefer: cleanup on error
- Use: resource management
// Using defer for cleanup
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const ptr = try alloc.create(i32);
defer alloc.destroy(ptr);
ptr.* = 42;
std.debug.print("Value: {}\n", .{ptr.*});
}Function pointers allow passing functions as parameters or storing them.
- Type:
*const fn (i32, i32) i32 - Assign:
const f = &add; - Call:
f(10, 5)
// Function pointers
const std = @import("std");
fn add(a: i32, b: i32) i32 { return a + b; }
fn sub(a: i32, b: i32) i32 { return a - b; }
pub fn main() !void {
const op: *const fn (i32, i32) i32 = &add;
const result = op(10, 5);
std.debug.print("Result: {}\n", .{result});
}Functions can accept comptime parameters (types, values) that are evaluated at compile time.
- Comptime parameter:
comptime T: type - Value:
comptime n: usize - Usage: generic functions, type generation
// Comptime parameters
const std = @import("std");
fn printType(comptime T: type, value: T) void {
std.debug.print("Type: {s}, value: {}\n", .{ @typeName(T), value });
}
pub fn main() !void {
printType(i32, 42);
printType(f64, 3.14);
printType([]const u8, "hello");
}Arrays can be nested to form matrices.
- Declaration:
var matrix: [2][3]i32 = ... - Access:
matrix[0][1]
// Multi-dimensional arrays
const std = @import("std");
pub fn main() !void {
var matrix: [2][3]i32 = .{
.{ 1, 2, 3 },
.{ 4, 5, 6 },
};
for (matrix) |row| {
for (row) |val| {
std.debug.print("{} ", .{val});
}
std.debug.print("\n", .{});
}
}Strings are UTF-8 encoded byte slices. Standard library provides utilities.
- String literal:
"hello"is[]const u8 - Slice:
s[0..5] - Concatenation:
std.mem.concat
// Slices as strings
const std = @import("std");
pub fn main() !void {
const s: []const u8 = "Hello Zig";
const first = s[0..5];
const last = s[6..];
std.debug.print("First: {s}, Last: {s}\n", .{ first, last });
}Use std.mem.reverse or manual iteration.
- In-place:
std.mem.reverse(u8, s) - Allocate:
allocator.allocand copy backwards
// String functions
const std = @import("std");
pub fn main() !void {
const text = "Hello World";
const len = text.len;
const sub = text[6..]; // "World"
const contains = std.mem.indexOf(u8, text, "World") != null;
const replaced = std.mem.replace(u8, text, "World", "Zig");
std.debug.print("len: {}, sub: {s}, contains: {}, replaced: {s}\n", .{ len, sub, contains, replaced });
}Compare characters from both ends.
- Two-pointer
- Ignore case:
std.ascii.toLower
// Reading files
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();
const size = try file.getEndPos();
const buffer = try alloc.alloc(u8, size);
defer alloc.free(buffer);
_ = try file.readAll(buffer);
std.debug.print("File content: {s}\n", .{buffer});
}Iterate and keep track of max.
- Linear scan
// Writing files
const std = @import("std");
pub fn main() !void {
const file = try std.fs.cwd().createFile("output.txt", .{});
defer file.close();
const data = "Hello Zig
";
_ = try file.write(data);
}Use a hash map to track seen elements.
- HashMap:
std.AutoHashMap - ArrayList: collect unique
// Command-line arguments
const std = @import("std");
pub fn main() !void {
const args = try std.process.argsAlloc(std.heap.page_allocator);
defer std.process.argsFree(std.heap.page_allocator, args);
for (args) |arg| {
std.debug.print("Arg: {s}\n", .{arg});
}
}Concatenate slices using allocator.
- ArrayList: append slices
// Random numbers
const std = @import("std");
pub fn main() !void {
var prng = std.rand.DefaultPrng.init(blk: {
var seed: u64 = undefined;
try std.os.getrandom(std.mem.asBytes(&seed));
break :blk seed;
});
const rand = prng.random();
const num = rand.int(i32);
const float = rand.float(f64);
const in_range = rand.intRangeLessThan(i32, 0, 100);
std.debug.print("num: {}, float: {}, range: {}\n", .{ num, float, in_range });
}Use std.fmt.parseInt.
- parseInt: returns
!i32
// JSON parsing (using std.json)
const std = @import("std");
pub fn main() !void {
const json_str =
\{ "name": "Alice", "age": 25 }
;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const parsed = try std.json.parseFromSlice(std.json.Value, alloc, json_str, .{});
defer parsed.deinit();
const name = parsed.value.object.get("name").?.string;
const age = parsed.value.object.get("age").?.integer;
std.debug.print("Name: {s}, Age: {}\n", .{ name, age });
}Use iterator on StringHashMap.
- iterator: while loop
// HashMap usage
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var map = std.StringHashMap(i32).init(alloc);
defer map.deinit();
try map.put("Alice", 25);
try map.put("Bob", 30);
if (map.get("Alice")) |age| {
std.debug.print("Alice's age: {}\n", .{age});
}
var it = map.iterator();
while (it.next()) |entry| {
std.debug.print("{s} => {}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
}
}Use std.time.sleep.
- Sleep:
std.time.sleep(ns)
// ArrayList usage
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var list = std.ArrayList(i32).init(alloc);
defer list.deinit();
try list.append(10);
try list.append(20);
try list.append(30);
for (list.items) |val| {
std.debug.print("{}\n", .{val});
}
}Zig's std.http can be used but requires setup.
- Client:
std.http.Client - Fetch: send request
// Sorting
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var list = std.ArrayList(i32).init(alloc);
defer list.deinit();
try list.appendSlice(&[_]i32{ 5, 2, 8, 1, 9 });
std.sort.sort(i32, list.items, {}, std.sort.asc(i32));
for (list.items) |val| {
std.debug.print("{}\n", .{val});
}
}Split by comma and iterate lines.
- split:
std.mem.split
// Custom sorting with comparator
const std = @import("std");
fn cmp(context: void, a: i32, b: i32) bool {
_ = context;
return a > b; // descending
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var arr = try alloc.alloc(i32, 5);
defer alloc.free(arr);
arr[0] = 5; arr[1] = 2; arr[2] = 8; arr[3] = 1; arr[4] = 9;
std.sort.sort(i32, arr, {}, cmp);
for (arr) |val| {
std.debug.print("{}\n", .{val});
}
}Recursive or iterative.
- Recursive:
n * factorial(n-1)
// Reverse string
fn reverseString(s: []const u8) []const u8 {
var result = s;
// In-place reverse (requires mutable slice)
// But we'll just return a reversed slice? Actually we need alloc.
// This is a placeholder.
return s;
}
pub fn main() !void {
const s = "hello";
// Not implemented fully; use reverse function from std.
}Recursive, iterative, or memoized.
- Iterative: O(n)
// Check palindrome
fn isPalindrome(s: []const u8) bool {
var i: usize = 0;
var j: usize = s.len - 1;
while (i < j) {
if (s[i] != s[j]) return false;
i += 1;
j -= 1;
}
return true;
}
pub fn main() !void {
const s1 = "racecar";
const s2 = "hello";
std.debug.print("{} {}\n", .{ isPalindrome(s1), isPalindrome(s2) });
}Loop and check divisibility.
- % operator
// Find max in array
fn findMax(arr: []const i32) i32 {
var max = arr[0];
for (arr[1..]) |v| {
if (v > max) max = v;
}
return max;
}
pub fn main() !void {
const arr = [_]i32{ 5, 2, 8, 1, 9 };
const max = findMax(&arr);
std.debug.print("Max: {}\n", .{max});
}Sum formula: total - sum.
- O(n)
// Remove duplicates
fn removeDuplicates(allocator: std.mem.Allocator, arr: []const i32) ![]i32 {
var map = std.AutoHashMap(i32, void).init(allocator);
defer map.deinit();
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
for (arr) |v| {
if (!map.contains(v)) {
try map.put(v, {});
try list.append(v);
}
}
return list.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const arr = [_]i32{ 1, 2, 2, 3, 3, 4 };
const unique = try removeDuplicates(alloc, &arr);
defer alloc.free(unique);
for (unique) |v| {
std.debug.print("{}\n", .{v});
}
}Use HashMap to count.
- Count > 1
// Merge arrays
fn mergeArrays(allocator: std.mem.Allocator, a: []const i32, b: []const i32) ![]i32 {
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
try list.appendSlice(a);
try list.appendSlice(b);
return list.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const a = [_]i32{1,2,3};
const b = [_]i32{4,5,6};
const merged = try mergeArrays(alloc, &a, &b);
defer alloc.free(merged);
for (merged) |v| {
std.debug.print("{}\n", .{v});
}
}Iterate and accumulate.
- for loop
// Convert string to number
fn stringToInt(s: []const u8) !i32 {
return try std.fmt.parseInt(i32, s, 10);
}
pub fn main() !void {
const s = "42";
const num = try stringToInt(s);
std.debug.print("Num: {}\n", .{num});
}Sum divided by length.
- float conversion
// Loop through HashMap
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var map = std.StringHashMap(i32).init(alloc);
defer map.deinit();
try map.put("Alice", 25);
try map.put("Bob", 30);
var it = map.iterator();
while (it.next()) |entry| {
std.debug.print("{s} => {}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
}
}Use std.sort.sort with ascending comparator.
- std.sort.asc
// Delay execution
const std = @import("std");
pub fn main() !void {
std.debug.print("Start\n", .{});
std.time.sleep(2 * std.time.ns_per_s);
std.debug.print("After 2 seconds\n", .{});
}Custom comparator for descending.
- a > b
// HTTP GET (using std.http)
// Requires connecting to a client, not trivial in pure Zig without std.http.Client.
// This is a placeholder.
const std = @import("std");
pub fn main() !void {
std.debug.print("HTTP GET not implemented in this example\n", .{});
}Manual flatten by iterating rows.
- 2D array
// CSV processing
const std = @import("std");
pub fn main() !void {
const csv = "Alice,25,NYC
Bob,30,LA
";
var lines = std.mem.split(u8, csv, "
");
while (lines.next()) |line| {
if (line.len == 0) continue;
var fields = std.mem.split(u8, line, ",");
const name = fields.next().?;
const age = fields.next().?;
const city = fields.next().?;
std.debug.print("Name: {s}, Age: {s}, City: {s}\n", .{ name, age, city });
}
}Loop with step and slice.
- while with chunk size
// Factorial (recursive)
fn factorial(n: u32) u32 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
pub fn main() !void {
const result = factorial(5);
std.debug.print("5! = {}\n", .{result});
}while loop with mid.
- O(log n)
// Fibonacci (recursive)
fn fib(n: u32) u32 {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
fn fib_iter(n: u32) u32 {
if (n <= 1) return n;
var a: u32 = 0;
var b: u32 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
const c = a + b;
a = b;
b = c;
}
return b;
}
pub fn main() !void {
const n = 10;
std.debug.print("fib({}) = {}, iter = {}\n", .{ n, fib(n), fib_iter(n) });
}Recursive partition.
- in-place
// FizzBuzz
pub fn main() !void {
var i: u32 = 1;
while (i <= 20) : (i += 1) {
if (i % 15 == 0) {
std.debug.print("FizzBuzz\n", .{});
} else if (i % 3 == 0) {
std.debug.print("Fizz\n", .{});
} else if (i % 5 == 0) {
std.debug.print("Buzz\n", .{});
} else {
std.debug.print("{}\n", .{i});
}
}
}Recursive merge with auxiliary arrays.
- allocator for temp
// Find missing number
fn findMissing(arr: []const u32) u32 {
const n = arr.len + 1;
const total = n * (n + 1) / 2;
var sum: u32 = 0;
for (arr) |v| sum += v;
return total - sum;
}
pub fn main() !void {
const arr = [_]u32{ 1, 2, 4, 5, 6 };
const missing = findMissing(&arr);
std.debug.print("Missing: {}\n", .{missing});
}Nested loops with swap.
- Optimized with swap flag
// Find duplicates
fn findDuplicates(allocator: std.mem.Allocator, arr: []const i32) ![]i32 {
var map = std.AutoHashMap(i32, u32).init(allocator);
defer map.deinit();
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
for (arr) |v| {
const entry = try map.getOrPut(v);
if (entry.found_existing) {
entry.value_ptr.* += 1;
} else {
entry.value_ptr.* = 1;
}
}
var it = map.iterator();
while (it.next()) |entry| {
if (entry.value_ptr.* > 1) {
try list.append(entry.key_ptr.*);
}
}
return list.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const arr = [_]i32{ 1, 2, 3, 2, 4, 3 };
const dup = try findDuplicates(alloc, &arr);
defer alloc.free(dup);
for (dup) |v| std.debug.print("{}\n", .{v});
}Use HashMap for membership.
- O(n+m)
// Sum of array
fn sumArray(arr: []const i32) i32 {
var total: i32 = 0;
for (arr) |v| total += v;
return total;
}
pub fn main() !void {
const arr = [_]i32{ 1, 2, 3, 4, 5 };
const s = sumArray(&arr);
std.debug.print("Sum: {}\n", .{s});
}Add all elements to HashMap.
- O(n+m)
// Average of array
fn averageArray(arr: []const i32) f64 {
var sum: i64 = 0;
for (arr) |v| sum += v;
return @as(f64, @floatFromInt(sum)) / @as(f64, @floatFromInt(arr.len));
}
pub fn main() !void {
const arr = [_]i32{ 1, 2, 3, 4, 5 };
const avg = averageArray(&arr);
std.debug.print("Average: {d}\n", .{avg});
}Elements in first not in second.
- HashMap for second
// Sort array ascending (using std.sort)
const std = @import("std");
pub fn main() !void {
var arr = [_]i32{5, 2, 8, 1, 9};
std.sort.sort(i32, &arr, {}, std.sort.asc(i32));
for (arr) |v| std.debug.print("{}\n", .{v});
}Use HashMap of ArrayList.
- City → people
// Sort array descending
const std = @import("std");
fn desc(context: void, a: i32, b: i32) bool {
_ = context;
return a > b;
}
pub fn main() !void {
var arr = [_]i32{5, 2, 8, 1, 9};
std.sort.sort(i32, &arr, {}, desc);
for (arr) |v| std.debug.print("{}\n", .{v});
}Copy struct fields, duplicate strings.
- allocator.dupe
// Flatten nested array (simplified)
const std = @import("std");
pub fn main() !void {
// In Zig, nested arrays are 2D arrays.
const matrix = [_][3]i32{ .{1,2,3}, .{4,5,6} };
var flat: [6]i32 = undefined;
var idx: usize = 0;
for (matrix) |row| {
for (row) |val| {
flat[idx] = val;
idx += 1;
}
}
for (flat) |v| std.debug.print("{}\n", .{v});
}Create new struct with updated field.
- Copy-on-write
// Chunk array
const std = @import("std");
pub fn main() !void {
const arr = [_]i32{1,2,3,4,5,6,7,8,9,10};
const chunk_size: usize = 3;
var i: usize = 0;
while (i < arr.len) {
const end = @min(i + chunk_size, arr.len);
const chunk = arr[i..end];
std.debug.print("Chunk: ", .{});
for (chunk) |v| std.debug.print("{} ", .{v});
std.debug.print("\n", .{});
i += chunk_size;
}
}Apply functions left-to-right.
- Array of function pointers
// Binary search
fn binarySearch(arr: []const i32, target: i32) ?usize {
var left: usize = 0;
var right: usize = arr.len;
while (left < right) {
const mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) left = mid + 1 else right = mid;
}
return null;
}
pub fn main() !void {
const arr = [_]i32{1,2,3,4,5,6,7};
const idx = binarySearch(&arr, 5);
if (idx) |i| std.debug.print("Found at {}\n", .{i}) else std.debug.print("Not found\n", .{});
}Right-to-left composition.
- Reverse iteration
// Quick sort (in-place)
fn partition(arr: []i32, low: usize, high: usize) usize {
const pivot = arr[high];
var i = low;
var j = low;
while (j < high) {
if (arr[j] <= pivot) {
const tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i += 1;
}
j += 1;
}
arr[high] = arr[i];
arr[i] = pivot;
return i;
}
fn quickSort(arr: []i32, low: usize, high: usize) void {
if (low < high) {
const pi = partition(arr, low, high);
if (pi > low) quickSort(arr, low, pi - 1);
if (pi < high) quickSort(arr, pi + 1, high);
}
}
pub fn main() !void {
var arr = [_]i32{5, 3, 8, 4, 2, 7, 1, 6};
quickSort(&arr, 0, arr.len - 1);
for (arr) |v| std.debug.print("{}\n", .{v});
}Cache results in HashMap.
- Fibonacci example
// Merge sort
fn merge(allocator: std.mem.Allocator, arr: []i32, left: usize, mid: usize, right: usize) !void {
const n1 = mid - left + 1;
const n2 = right - mid;
const L = try allocator.alloc(i32, n1);
defer allocator.free(L);
const R = try allocator.alloc(i32, n2);
defer allocator.free(R);
for (0..n1) |i| L[i] = arr[left + i];
for (0..n2) |i| R[i] = arr[mid + 1 + i];
var i: usize = 0;
var j: usize = 0;
var k: usize = left;
while (i < n1 and j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i += 1;
} else {
arr[k] = R[j];
j += 1;
}
k += 1;
}
while (i < n1) {
arr[k] = L[i];
i += 1;
k += 1;
}
while (j < n2) {
arr[k] = R[j];
j += 1;
k += 1;
}
}
fn mergeSort(allocator: std.mem.Allocator, arr: []i32, left: usize, right: usize) !void {
if (left < right) {
const mid = left + (right - left) / 2;
try mergeSort(allocator, arr, left, mid);
try mergeSort(allocator, arr, mid + 1, right);
try merge(allocator, arr, left, mid, right);
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var arr = [_]i32{5, 3, 8, 4, 2, 7, 1, 6};
try mergeSort(alloc, &arr, 0, arr.len - 1);
for (arr) |v| std.debug.print("{}\n", .{v});
}State variable to track execution.
- Global flag
// Bubble sort
fn bubbleSort(arr: []i32) void {
for (0..arr.len) |i| {
var swapped = false;
for (0..arr.len - i - 1) |j| {
if (arr[j] > arr[j+1]) {
const tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
swapped = true;
}
}
if (!swapped) break;
}
}
pub fn main() !void {
var arr = [_]i32{5, 3, 8, 4, 2, 7, 1, 6};
bubbleSort(&arr);
for (arr) |v| std.debug.print("{}\n", .{v});
}Check time difference before executing.
- std.time.timestamp
// Intersection of arrays
fn intersection(allocator: std.mem.Allocator, a: []const i32, b: []const i32) ![]i32 {
var map = std.AutoHashMap(i32, void).init(allocator);
defer map.deinit();
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
for (a) |v| try map.put(v, {});
for (b) |v| {
if (map.contains(v)) {
try list.append(v);
_ = map.remove(v); // avoid duplicates
}
}
return list.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const a = [_]i32{1,2,3,4,5};
const b = [_]i32{4,5,6,7,8};
const inter = try intersection(alloc, &a, &b);
defer alloc.free(inter);
for (inter) |v| std.debug.print("{}\n", .{v});
}Execute at most once per interval.
- Timestamp
// Union of arrays
fn union(allocator: std.mem.Allocator, a: []const i32, b: []const i32) ![]i32 {
var map = std.AutoHashMap(i32, void).init(allocator);
defer map.deinit();
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
for (a) |v| try map.put(v, {});
for (b) |v| try map.put(v, {});
var it = map.iterator();
while (it.next()) |entry| {
try list.append(entry.key_ptr.*);
}
return list.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const a = [_]i32{1,2,3};
const b = [_]i32{3,4,5};
const un = try union(alloc, &a, &b);
defer alloc.free(un);
for (un) |v| std.debug.print("{}\n", .{v});
}Compare byte slices for equality.
- std.mem.eql
// Difference of arrays
fn difference(allocator: std.mem.Allocator, a: []const i32, b: []const i32) ![]i32 {
var map = std.AutoHashMap(i32, void).init(allocator);
defer map.deinit();
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
for (b) |v| try map.put(v, {});
for (a) |v| {
if (!map.contains(v)) {
try list.append(v);
}
}
return list.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const a = [_]i32{1,2,3,4,5};
const b = [_]i32{3,4,5};
const diff = try difference(alloc, &a, &b);
defer alloc.free(diff);
for (diff) |v| std.debug.print("{}\n", .{v});
}Simple notify function.
- Observer list
// Group by property (using structs)
const std = @import("std");
const Person = struct {
name: []const u8,
age: u8,
city: []const u8,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const people = [_]Person{
.{ .name = "Alice", .age = 25, .city = "NYC" },
.{ .name = "Bob", .age = 30, .city = "LA" },
.{ .name = "Charlie", .age = 25, .city = "NYC" },
};
var groups = std.StringHashMap(std.ArrayList(Person)).init(alloc);
defer {
var it = groups.iterator();
while (it.next()) |entry| {
entry.value_ptr.*.deinit();
}
groups.deinit();
}
for (people) |p| {
const entry = try groups.getOrPut(p.city);
if (!entry.found_existing) {
entry.value_ptr.* = std.ArrayList(Person).init(alloc);
}
try entry.value_ptr.*.append(p);
}
var it = groups.iterator();
while (it.next()) |entry| {
std.debug.print("City: {s}\n", .{entry.key_ptr.*});
for (entry.value_ptr.*.items) |person| {
std.debug.print(" {} ({})\n", .{ person.name, person.age });
}
}
}Global variable initialized once.
- Global
// Deep clone (simple copy)
const std = @import("std");
const Data = struct {
a: i32,
b: []const u8,
};
fn cloneData(allocator: std.mem.Allocator, d: Data) !Data {
const b_clone = try allocator.dupe(u8, d.b);
return .{ .a = d.a, .b = b_clone };
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const orig = Data{ .a = 42, .b = "hello" };
const cloned = try cloneData(alloc, orig);
defer alloc.free(cloned.b);
std.debug.print("orig: {}, cloned: {}, orig.b: {s}, cloned.b: {s}\n", .{ orig.a, cloned.a, orig.b, cloned.b });
}Return tagged union based on input.
- Union
// Immutable update (copy-on-write)
const std = @import("std");
const State = struct {
count: i32,
};
fn updateState(state: State, new_count: i32) State {
return State{ .count = new_count };
}
pub fn main() !void {
const s1 = State{ .count = 10 };
const s2 = updateState(s1, 20);
std.debug.print("s1: {}, s2: {}\n", .{ s1.count, s2.count });
}Function pointers for different strategies.
- PaymentStrategy
// Pipe function (compose)
fn pipe(value: i32, fns: []const fn (i32) i32) i32 {
var result = value;
for (fns) |f| {
result = f(result);
}
return result;
}
fn double(x: i32) i32 { return x * 2; }
fn addTen(x: i32) i32 { return x + 10; }
fn square(x: i32) i32 { return x * x; }
pub fn main() !void {
const fns = [_]*const fn (i32) i32{ &double, &addTen, &square };
const result = pipe(5, &fns);
std.debug.print("Result: {}\n", .{result});
}Maintain list of observers and notify.
- Array of observers
// Compose (right-to-left)
fn compose(value: i32, fns: []const fn (i32) i32) i32 {
var result = value;
var i: usize = fns.len;
while (i > 0) {
i -= 1;
result = fns[i](result);
}
return result;
}
fn double(x: i32) i32 { return x * 2; }
fn addTen(x: i32) i32 { return x + 10; }
fn square(x: i32) i32 { return x * x; }
pub fn main() !void {
const fns = [_]*const fn (i32) i32{ &double, &addTen, &square };
const result = compose(5, &fns);
std.debug.print("Result: {}\n", .{result});
}Chain functions that modify output.
- Function composition
// Memoization (Fibonacci)
const std = @import("std");
var memo = std.AutoHashMap(u32, u32).init(std.heap.page_allocator);
fn fibMemo(n: u32) !u32 {
if (n <= 1) return n;
if (memo.contains(n)) return memo.get(n).?;
const result = try fibMemo(n - 1) + try fibMemo(n - 2);
try memo.put(n, result);
return result;
}
pub fn main() !void {
defer memo.deinit();
const result = try fibMemo(10);
std.debug.print("fib(10) = {}\n", .{result});
}Function that modifies state.
- Command function
// Once function (state)
fn once() i32 {
var called = false;
var result: i32 = 0;
if (!called) {
called = true;
result = 42;
}
return result;
}
pub fn main() !void {
std.debug.print("{}\n", .{once()});
std.debug.print("{}\n", .{once()});
}Save and restore state.
- Global saved variable
// Debounce (simplified)
// Not natively supported; use time.
const std = @import("std");
var last_time: i64 = 0;
fn debounce(action: fn() void) void {
const now = std.time.timestamp();
if (now - last_time > 2) {
last_time = now;
action();
}
}
fn printHello() void {
std.debug.print("Hello\n", .{});
}
pub fn main() !void {
debounce(printHello);
std.time.sleep(1 * std.time.ns_per_s);
debounce(printHello);
std.time.sleep(3 * std.time.ns_per_s);
debounce(printHello);
}Central mediator for communication.
- struct with colleagues
// Throttle (simplified)
var last_call_time: i64 = 0;
fn throttle(action: fn() void) void {
const now = std.time.timestamp();
if (now - last_call_time >= 2) {
last_call_time = now;
action();
}
}
fn printWorld() void {
std.debug.print("World\n", .{});
}
pub fn main() !void {
throttle(printWorld);
throttle(printWorld);
std.time.sleep(3 * std.time.ns_per_s);
throttle(printWorld);
}Array of handlers processed in order.
- Break on failure
// Deep equal (simple)
fn deepEqual(a: []const u8, b: []const u8) bool {
return std.mem.eql(u8, a, b);
}
pub fn main() !void {
const s1 = "hello";
const s2 = "hello";
const s3 = "world";
std.debug.print("{} {}\n", .{ deepEqual(s1, s2), deepEqual(s1, s3) });
}State variable changes behavior.
- Switch on state
// Observable pattern (simple)
const std = @import("std");
fn notify(value: i32) void {
std.debug.print("Observer received: {}\n", .{value});
}
pub fn main() !void {
const data: i32 = 42;
notify(data);
}Check access before forwarding.
- Authentication
// Singleton pattern (global)
var instance: i32 = 0;
fn getInstance() i32 {
if (instance == 0) {
instance = 42;
}
return instance;
}
pub fn main() !void {
std.debug.print("{}\n", .{getInstance()});
std.debug.print("{}\n", .{getInstance()});
}Reuse shared objects.
- Cache by key
// Factory pattern
const User = union(enum) {
admin: []const u8,
guest: []const u8,
regular: []const u8,
};
fn createUser(role: []const u8, name: []const u8) User {
if (std.mem.eql(u8, role, "admin")) {
return .{ .admin = name };
} else if (std.mem.eql(u8, role, "guest")) {
return .{ .guest = name };
} else {
return .{ .regular = name };
}
}
pub fn main() !void {
const user = createUser("admin", "Alice");
switch (user) {
.admin => |name| std.debug.print("Admin: {s}\n", .{name}),
.guest => |name| std.debug.print("Guest: {s}\n", .{name}),
.regular => |name| std.debug.print("Regular: {s}\n", .{name}),
}
}Abstraction uses implementation function.
- Function pointer
// Strategy pattern
const std = @import("std");
const PaymentStrategy = fn (amount: f64) void;
fn creditCard(amount: f64) void {
std.debug.print("Paid ${d:.2} with Credit Card\n", .{amount});
}
fn payPal(amount: f64) void {
std.debug.print("Paid ${d:.2} with PayPal\n", .{amount});
}
fn crypto(amount: f64) void {
std.debug.print("Paid ${d:.2} with Crypto\n", .{amount});
}
pub fn main() !void {
const strategies = [_]PaymentStrategy{ creditCard, payPal, crypto };
for (strategies) |s| {
s(100.0);
}
}Wrap adaptee function.
- Adapter calls adaptee
// Observer pattern (more complete)
const std = @import("std");
const Observer = struct {
name: []const u8,
update: fn ([]const u8) void,
};
var observers: [10]Observer = undefined;
var count: usize = 0;
fn attach(obs: Observer) void {
observers[count] = obs;
count += 1;
}
fn notify(data: []const u8) void {
for (0..count) |i| {
observers[i].update(data);
}
}
fn printObserver(name: []const u8) void {
std.debug.print("Observer received: {s}\n", .{name});
}
pub fn main() !void {
const obs1 = Observer{ .name = "obs1", .update = printObserver };
const obs2 = Observer{ .name = "obs2", .update = printObserver };
attach(obs1);
attach(obs2);
notify("Hello");
}Simplified interface to subsystems.
- Facade function
// Decorator pattern
const std = @import("std");
fn coffee() []const u8 { return "Coffee"; }
fn milk(decorated: []const u8) []const u8 { return decorated ++ ", Milk"; }
fn sugar(decorated: []const u8) []const u8 { return decorated ++ ", Sugar"; }
pub fn main() !void {
const base = coffee();
const withMilk = milk(base);
const withSugar = sugar(withMilk);
std.debug.print("{s}\n", .{withSugar});
}Tree of components using union.
- Union of leaf and composite
// Command pattern
const std = @import("std");
const Command = fn (i32, i32) i32;
fn add(value: i32, current: i32) i32 { return current + value; }
fn sub(value: i32, current: i32) i32 { return current - value; }
pub fn main() !void {
var state: i32 = 0;
const cmd1: Command = add;
const cmd2: Command = sub;
state = cmd1(5, state);
state = cmd2(3, state);
std.debug.print("State: {}\n", .{state});
}Apply different operations on elements.
- Visit functions
// Memento pattern
const std = @import("std");
var saved_state: i32 = 0;
fn save(s: i32) void { saved_state = s; }
fn restore() i32 { return saved_state; }
pub fn main() !void {
var state: i32 = 10;
save(state);
state = 20;
state = restore();
std.debug.print("State: {}\n", .{state});
}Custom iterator with next method.
- struct with index
// Mediator pattern
const std = @import("std");
const Colleague = struct {
name: []const u8,
mediator: *Mediator,
};
const Mediator = struct {
colleagues: [10]Colleague = undefined,
count: usize = 0,
fn register(self: *Mediator, c: Colleague) void {
self.colleagues[self.count] = c;
self.count += 1;
}
fn send(self: *Mediator, msg: []const u8, sender: []const u8) void {
for (0..self.count) |i| {
if (!std.mem.eql(u8, self.colleagues[i].name, sender)) {
std.debug.print("{s} received: {s}\n", .{ self.colleagues[i].name, msg });
}
}
}
};
pub fn main() !void {
var mediator = Mediator{};
const alice = Colleague{ .name = "Alice", .mediator = &mediator };
const bob = Colleague{ .name = "Bob", .mediator = &mediator };
mediator.register(alice);
mediator.register(bob);
mediator.send("Hello", "Alice");
}Define skeleton with customizable steps.
- Fixed sequence of calls
// Chain of Responsibility
const std = @import("std");
const Handler = fn (request: []const u8) bool;
fn authHandler(request: []const u8) bool {
if (std.mem.indexOf(u8, request, "token") != null) {
std.debug.print("Auth passed\n", .{});
return true;
}
std.debug.print("Auth failed\n", .{});
return false;
}
fn loggerHandler(request: []const u8) bool {
std.debug.print("Logging: {s}\n", .{request});
return true;
}
pub fn main() !void {
const chain = [_]Handler{ authHandler, loggerHandler };
const req = "token: valid";
for (chain) |h| {
if (!h(req)) break;
}
}Step-by-step construction.
- Build functions
// State pattern
const std = @import("std");
var state: u8 = 0;
fn transition() void {
state = (state + 1) % 3;
}
pub fn main() !void {
for (0..5) |_| {
std.debug.print("State: {}\n", .{state});
transition();
}
}Clone objects.
- dupe for strings
// Proxy pattern
const std = @import("std");
fn realRequest() void {
std.debug.print("Real request\n", .{});
}
fn proxyRequest(authenticated: bool) void {
if (authenticated) {
std.debug.print("Proxy: access granted\n", .{});
realRequest();
} else {
std.debug.print("Proxy: access denied\n", .{});
}
}
pub fn main() !void {
proxyRequest(true);
proxyRequest(false);
}Use error unions and try/catch.
- try, catch
// Flyweight pattern
const std = @import("std");
var flyweights: [10][]const u8 = undefined;
var fcount: usize = 0;
fn getFlyweight(key: []const u8) []const u8 {
for (0..fcount) |i| {
if (std.mem.eql(u8, flyweights[i], key)) return flyweights[i];
}
flyweights[fcount] = key;
fcount += 1;
return key;
}
pub fn main() !void {
const s1 = getFlyweight("state1");
const s2 = getFlyweight("state1");
const s3 = getFlyweight("state2");
std.debug.print("{} {} {}\n", .{ s1, s2, s3 });
}Use std.json to parse/generate JSON.
- parseFromSlice
// Bridge pattern
const std = @import("std");
const Implementation = fn () void;
fn implA() void { std.debug.print("Impl A\n", .{}); }
fn implB() void { std.debug.print("Impl B\n", .{}); }
fn abstraction(impl: Implementation) void {
std.debug.print("Abstraction: ", .{});
impl();
}
pub fn main() !void {
abstraction(implA);
abstraction(implB);
}Use @TypeOf, @typeInfo.
- Comptime
// Adapter pattern
const std = @import("std");
fn targetRequest() void { std.debug.print("Target\n", .{}); }
fn adapteeRequest() void { std.debug.print("Adaptee\n", .{}); }
fn adapter() void { adapteeRequest(); }
pub fn main() !void {
targetRequest();
adapter();
}Compose functionality by including fields/functions.
- Struct embedding
// Facade pattern
const std = @import("std");
fn subA() void { std.debug.print("A\n", .{}); }
fn subB() void { std.debug.print("B\n", .{}); }
fn subC() void { std.debug.print("C\n", .{}); }
fn facade() void {
subA();
subB();
subC();
}
pub fn main() !void {
facade();
}Check types at runtime with @typeInfo.
- Comptime
// Composite pattern
const std = @import("std");
const Component = union(enum) {
leaf: []const u8,
composite: struct {
name: []const u8,
children: []const Component,
},
};
pub fn main() !void {
const leaf1 = Component{ .leaf = "A" };
const leaf2 = Component{ .leaf = "B" };
const comp = Component{ .composite = .{
.name = "Root",
.children = &[_]Component{ leaf1, leaf2 },
} };
std.debug.print("Composite: {s}\n", .{comp.composite.name});
}anytype for generic parameters, opaque for incomplete types.
- anytype
- opaque
// Visitor pattern
const std = @import("std");
fn visitA(element: []const u8) void {
std.debug.print("Visit A: {s}\n", .{element});
}
fn visitB(element: []const u8) void {
std.debug.print("Visit B: {s}\n", .{element});
}
pub fn main() !void {
const elements = [_][]const u8{ "Hello", "World" };
for (elements) |e| visitA(e);
for (elements) |e| visitB(e);
}Use C libraries via FFI.
- @cImport
// Iterator pattern
const std = @import("std");
const Iterator = struct {
data: []const i32,
index: usize = 0,
fn next(self: *Iterator) ?i32 {
if (self.index < self.data.len) {
const val = self.data[self.index];
self.index += 1;
return val;
}
return null;
}
};
pub fn main() !void {
const arr = [_]i32{ 1, 2, 3, 4, 5 };
var it = Iterator{ .data = &arr };
while (it.next()) |val| {
std.debug.print("{}\n", .{val});
}
}Write custom serialization using format strings.
- std.fmt
// Template Method pattern
const std = @import("std");
fn step1() void { std.debug.print("Step1\n", .{}); }
fn step2() void { std.debug.print("Step2\n", .{}); }
fn step3() void { std.debug.print("Step3\n", .{}); }
fn template() void {
step1();
step2();
step3();
}
pub fn main() !void {
template();
}Use std.debug print, breakpoints, and error traces.
- std.debug.print
- @breakpoint
// Builder pattern
const std = @import("std");
var product: []const u8 = "";
fn buildA() void { product = product ++ "A"; }
fn buildB() void { product = product ++ "B"; }
fn getResult() []const u8 { return product; }
pub fn main() !void {
buildA();
buildB();
std.debug.print("Product: {s}\n", .{getResult()});
}Use comptime, avoid allocations, profile.
- Comptime for constants
- FixedBufferAllocator for stack
// Prototype pattern
const Data = struct {
a: i32,
b: []const u8,
};
fn cloneData(allocator: std.mem.Allocator, d: Data) !Data {
const b_clone = try allocator.dupe(u8, d.b);
return .{ .a = d.a, .b = b_clone };
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const orig = Data{ .a = 42, .b = "hello" };
const cloned = try cloneData(alloc, orig);
defer alloc.free(cloned.b);
std.debug.print("Original: {s}, Clone: {s}\n", .{ orig.b, cloned.b });
}