InterviewPitch
Zig interview questions

Zig Interview Questions with Answers

Most Asked Zig Interview Questions for Software Engineer Roles

100+ QuestionsDetailed AnswersCode ExamplesUpdated for 2026

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

Beginner
1. What is Zig?

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
zig
// Hello World in Zig
const std = @import("std");

pub fn main() !void {
    std.debug.print("Hello, World!\n", .{});
}
Beginner
2. How to declare variables in Zig?

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
zig
// 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 });
}
Beginner
3. What are the data types in Zig?

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
zig
// 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 });
}
Beginner
4. How to define functions in Zig?

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
zig
// 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 });
}
Beginner
5. What are arrays and slices in Zig?

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: len and slice.len
zig
// 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});
}
Beginner
6. How to allocate memory in Zig?

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)
zig
// 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});
    }
}
Beginner
7. How to define and use structs?

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
zig
// 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 });
}
Beginner
8. What are enums and unions in Zig?

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: switch with union
zig
// 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", .{}),
    }
}
Beginner
9. How does Zig handle optionals?

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;
zig
// 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});
}
Beginner
10. How does error handling work in Zig?

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
zig
// 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});
    };
}
Beginner
11. Control flow statements in Zig

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) { ... }
zig
// 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;
    }
}
Intermediate
12. What is comptime in Zig?

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
zig
// 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});
}
Intermediate
13. How to use allocators in Zig?

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)
zig
// 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]});
}
Intermediate
14. What are pointers in Zig?

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
zig
// 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.* });
}
Intermediate
15. Generics in Zig (comptime types)

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)
zig
// 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});
    }
}
Intermediate
16. How does Zig handle imports and the build system?

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
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", .{});
}
Intermediate
17. Testing in Zig

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
zig
// 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;
}
Intermediate
18. What is comptime reflection?

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
zig
// 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 });
    }
}
Advanced
19. How to use async/await in Zig?

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
zig
// 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});
}
Advanced
20. Bit operations in Zig

Zig provides bitwise operators: &, |, ^, ~, <<, >>.

  • And: a & b
  • Or: a | b
  • Xor: a ^ b
  • Not: ~a
  • Shift: a << 1
zig
// 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 });
}
Advanced
21. Error sets in Zig

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
zig
// 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});
}
Advanced
22. Custom allocators

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
zig
// 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});
}
Advanced
23. C Interoperability (FFI)

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 { ... }
zig
// 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
");
}
Advanced
24. Packed structs and bitfields

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
zig
// 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 });
}
Advanced
25. Inline loops in Zig

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
// 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", .{});
}
Advanced
26. Defer and errdefer

defer runs on scope exit, errdefer runs only on error path.

  • defer: cleanup always
  • errdefer: cleanup on error
  • Use: resource management
zig
// 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.*});
}
Advanced
27. Function pointers

Function pointers allow passing functions as parameters or storing them.

  • Type: *const fn (i32, i32) i32
  • Assign: const f = &add;
  • Call: f(10, 5)
zig
// 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});
}
Advanced
28. Comptime parameters

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
zig
// 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");
}
Advanced
29. Multidimensional arrays

Arrays can be nested to form matrices.

  • Declaration: var matrix: [2][3]i32 = ...
  • Access: matrix[0][1]
zig
// 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", .{});
    }
}
Advanced
30. Slicing and string manipulation

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
zig
// 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 });
}
Beginner
31. Reverse a string

Use std.mem.reverse or manual iteration.

  • In-place: std.mem.reverse(u8, s)
  • Allocate: allocator.alloc and copy backwards
zig
// 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 });
}
Coding Round
32. Check palindrome

Compare characters from both ends.

  • Two-pointer
  • Ignore case: std.ascii.toLower
zig
// 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});
}
Coding Round
33. Find max in array

Iterate and keep track of max.

  • Linear scan
zig
// 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);
}
Coding Round
34. Remove duplicates

Use a hash map to track seen elements.

  • HashMap: std.AutoHashMap
  • ArrayList: collect unique
zig
// 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});
    }
}
Coding Round
35. Merge two arrays

Concatenate slices using allocator.

  • ArrayList: append slices
zig
// 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 });
}
Coding Round
36. Convert string to number

Use std.fmt.parseInt.

  • parseInt: returns !i32
zig
// 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 });
}
Coding Round
37. Loop through HashMap

Use iterator on StringHashMap.

  • iterator: while loop
zig
// 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.* });
    }
}
Coding Round
38. Delay execution

Use std.time.sleep.

  • Sleep: std.time.sleep(ns)
zig
// 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});
    }
}
Advanced
39. HTTP GET request

Zig's std.http can be used but requires setup.

  • Client: std.http.Client
  • Fetch: send request
zig
// 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});
    }
}
Coding Round
40. Process CSV

Split by comma and iterate lines.

  • split: std.mem.split
zig
// 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});
    }
}
Coding Round
41. Factorial

Recursive or iterative.

  • Recursive: n * factorial(n-1)
zig
// 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.
}
Coding Round
42. Fibonacci

Recursive, iterative, or memoized.

  • Iterative: O(n)
zig
// 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) });
}
Coding Round
43. FizzBuzz

Loop and check divisibility.

  • % operator
zig
// 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});
}
Coding Round
44. Find missing number

Sum formula: total - sum.

  • O(n)
zig
// 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});
    }
}
Coding Round
45. Find duplicates

Use HashMap to count.

  • Count > 1
zig
// 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});
    }
}
Coding Round
46. Sum of array

Iterate and accumulate.

  • for loop
zig
// 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});
}
Coding Round
47. Average of array

Sum divided by length.

  • float conversion
zig
// 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.* });
    }
}
Coding Round
48. Sort array ascending

Use std.sort.sort with ascending comparator.

  • std.sort.asc
zig
// 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", .{});
}
Coding Round
49. Sort array descending

Custom comparator for descending.

  • a > b
zig
// 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", .{});
}
Coding Round
50. Flatten nested array

Manual flatten by iterating rows.

  • 2D array
zig
// 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 });
    }
}
Coding Round
51. Chunk array

Loop with step and slice.

  • while with chunk size
zig
// 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});
}
Coding Round
53. Quick sort

Recursive partition.

  • in-place
zig
// 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});
        }
    }
}
Coding Round
54. Merge sort

Recursive merge with auxiliary arrays.

  • allocator for temp
zig
// 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});
}
Coding Round
55. Bubble sort

Nested loops with swap.

  • Optimized with swap flag
zig
// 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});
}
Coding Round
56. Intersection of arrays

Use HashMap for membership.

  • O(n+m)
zig
// 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});
}
Coding Round
57. Union of arrays

Add all elements to HashMap.

  • O(n+m)
zig
// 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});
}
Coding Round
58. Difference of arrays

Elements in first not in second.

  • HashMap for second
zig
// 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});
}
Coding Round
59. Group by property

Use HashMap of ArrayList.

  • City → people
zig
// 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});
}
Coding Round
60. Deep clone

Copy struct fields, duplicate strings.

  • allocator.dupe
zig
// 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});
}
Coding Round
61. Immutable update

Create new struct with updated field.

  • Copy-on-write
zig
// 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;
    }
}
Coding Round
62. Pipe function

Apply functions left-to-right.

  • Array of function pointers
zig
// 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", .{});
}
Coding Round
63. Compose function

Right-to-left composition.

  • Reverse iteration
zig
// 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});
}
Coding Round
64. Memoization

Cache results in HashMap.

  • Fibonacci example
zig
// 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});
}
Coding Round
65. Once function

State variable to track execution.

  • Global flag
zig
// 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});
}
Coding Round
66. Debounce

Check time difference before executing.

  • std.time.timestamp
zig
// 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});
}
Coding Round
67. Throttle

Execute at most once per interval.

  • Timestamp
zig
// 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});
}
Coding Round
68. Deep equal

Compare byte slices for equality.

  • std.mem.eql
zig
// 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});
}
Coding Round
69. Observable pattern

Simple notify function.

  • Observer list
zig
// 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 });
        }
    }
}
Coding Round
70. Singleton pattern

Global variable initialized once.

  • Global
zig
// 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 });
}
Coding Round
71. Factory pattern

Return tagged union based on input.

  • Union
zig
// 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 });
}
Coding Round
72. Strategy pattern

Function pointers for different strategies.

  • PaymentStrategy
zig
// 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});
}
Coding Round
73. Observer pattern

Maintain list of observers and notify.

  • Array of observers
zig
// 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});
}
Coding Round
74. Decorator pattern

Chain functions that modify output.

  • Function composition
zig
// 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});
}
Coding Round
75. Command pattern

Function that modifies state.

  • Command function
zig
// 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()});
}
Coding Round
76. Memento pattern

Save and restore state.

  • Global saved variable
zig
// 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);
}
Coding Round
77. Mediator pattern

Central mediator for communication.

  • struct with colleagues
zig
// 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);
}
Coding Round
78. Chain of Responsibility

Array of handlers processed in order.

  • Break on failure
zig
// 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) });
}
Coding Round
79. State pattern

State variable changes behavior.

  • Switch on state
zig
// 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);
}
Coding Round
80. Proxy pattern

Check access before forwarding.

  • Authentication
zig
// 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()});
}
Coding Round
81. Flyweight pattern

Reuse shared objects.

  • Cache by key
zig
// 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}),
    }
}
Coding Round
82. Bridge pattern

Abstraction uses implementation function.

  • Function pointer
zig
// 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);
    }
}
Coding Round
83. Adapter pattern

Wrap adaptee function.

  • Adapter calls adaptee
zig
// 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");
}
Coding Round
84. Facade pattern

Simplified interface to subsystems.

  • Facade function
zig
// 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});
}
Coding Round
85. Composite pattern

Tree of components using union.

  • Union of leaf and composite
zig
// 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});
}
Coding Round
86. Visitor pattern

Apply different operations on elements.

  • Visit functions
zig
// 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});
}
Coding Round
87. Iterator pattern

Custom iterator with next method.

  • struct with index
zig
// 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");
}
Coding Round
88. Template Method pattern

Define skeleton with customizable steps.

  • Fixed sequence of calls
zig
// 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;
    }
}
Coding Round
89. Builder pattern

Step-by-step construction.

  • Build functions
zig
// 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();
    }
}
Coding Round
90. Prototype pattern

Clone objects.

  • dupe for strings
zig
// 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);
}
Advanced
91. Error handling patterns

Use error unions and try/catch.

  • try, catch
zig
// 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 });
}
Advanced
92. Serialization (JSON)

Use std.json to parse/generate JSON.

  • parseFromSlice
zig
// 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);
}
Advanced
93. Type assertions

Use @TypeOf, @typeInfo.

  • Comptime
zig
// 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();
}
Advanced
94. Mixins (using structs)

Compose functionality by including fields/functions.

  • Struct embedding
zig
// 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();
}
Advanced
95. Type guards

Check types at runtime with @typeInfo.

  • Comptime
zig
// 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});
}
Advanced
96. Advanced types (opaque, anytype)

anytype for generic parameters, opaque for incomplete types.

  • anytype
  • opaque
zig
// 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);
}
Advanced
97. DOM manipulation (via C)

Use C libraries via FFI.

  • @cImport
zig
// 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});
    }
}
Advanced
98. Serialization (custom)

Write custom serialization using format strings.

  • std.fmt
zig
// 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();
}
Advanced
99. Debugging techniques

Use std.debug print, breakpoints, and error traces.

  • std.debug.print
  • @breakpoint
zig
// 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()});
}
Advanced
100. Performance optimization

Use comptime, avoid allocations, profile.

  • Comptime for constants
  • FixedBufferAllocator for stack
zig
// 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 });
}