Zig Mastery (Senior → Principal)
Operate
- Start by confirming: Zig version, target OS/arch, deployment model, latency and memory goals, FFI requirements, concurrency model, and the definition of done.
- Prefer small vertical slices with explicit ownership, allocator strategy, and error paths.
- Keep the design boring and operable: simple boundaries, measurable behavior, and predictable cleanup beat clever abstractions.
- Treat memory, failure handling, and observability as first-class design constraints, not afterthoughts.
The goal is not just "fast Zig". The goal is a backend that stays correct under pressure, exposes predictable failure modes, and remains maintainable by the next engineer.
Default Standards
- Keep transport, domain, and infrastructure boundaries explicit.
- Choose allocator strategy intentionally and document ownership at boundaries.
- Propagate errors with context; do not hide them behind catch-all defaults.
- Prefer explicit resource lifecycle management with
defer and errdefer.
- Avoid hidden global state; inject dependencies and configuration explicitly.
- Treat outbound IO, parsing, and serialization as untrusted work: set limits, timeouts, and validation rules.
- Use the standard library first unless a dependency clearly improves correctness or delivery speed.
“Bad vs Good” (common production pitfalls)
// ❌ BAD: allocator choice is implicit and cleanup is forgotten.
const data = try fetchUsers();
process(data);
// ✅ GOOD: allocator ownership and cleanup are explicit.
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const data = try fetchUsers(allocator);
try process(data);
// ❌ BAD: panic-style behavior for recoverable backend failures.
const body = request.reader().readAllAlloc(allocator, max_size) catch unreachable;
// ✅ GOOD: map recoverable errors explicitly.
const body = request.reader().readAllAlloc(allocator, max_size) catch |err| switch (err) {
error.StreamTooLong => return AppError.payload_too_large,
else => return AppError.bad_request,
};
// ❌ BAD: fire-and-forget thread with no owner or shutdown path.
_ = try std.Thread.spawn(.{}, runWorker, .{});
// ✅ GOOD: thread lifecycle belongs to a supervisor.
const worker = try std.Thread.spawn(.{}, runWorker, .{shutdown_signal});
defer worker.join();
Workflow (Feature / Refactor / Bug)
- Reproduce behavior or codify it with a failing test.
- Decide boundaries: protocol, orchestration, domain logic, persistence, and external integrations.
- Define allocator strategy, ownership, and cleanup rules.
- Implement the smallest end-to-end slice.
- Validate tests, formatting, release behavior, and operational guardrails.
- Review latency, allocations, failure modes, and shutdown behavior before release.
Validation Commands
- Run
zig fmt ..
- Run
zig test src/main.zig or the relevant test targets.
- Run
zig build test if the project uses a build graph.
- Run
zig build -Doptimize=ReleaseSafe before release validation.
- Run smoke tests against the compiled binary in a production-like environment.
- Run container build validation if the service is deployed via Docker.
Backend Architecture Guardrails
- Prefer a modular monolith before inventing service sprawl.
- Keep HTTP or TCP transport thin; map protocol concerns at the edge.
- Make timeouts, retries, backoff, and circuit breaking explicit for outbound calls.
- Bound request size, parsing depth, and connection counts.
- Treat memory pressure as an operational event: measure allocations and cap untrusted workloads.
- Every background thread or async task needs an owner, stop condition, and failure-reporting path.
Reliability and Operations
- Expose
/health and /ready style endpoints if the service runs behind orchestration.
- Use structured logs with request IDs and stable fields.
- Implement graceful shutdown: stop accepting traffic, drain in-flight work, release resources, and join workers.
- Prefer idempotent handlers where retries may occur.
- Benchmark hot paths before micro-optimizing.
Security Checklist (Minimum)
- Validate all untrusted input lengths, counts, and encodings.
- Use allowlists for outbound destinations in sensitive environments.
- Never log secrets, credentials, or raw sensitive payloads.
- Use parameterized queries and least-privilege credentials for data stores.
- Keep unsafe FFI boundaries isolated and well-tested.
Decision Heuristics
Choose Zig when:
- predictable memory behavior matters
- latency and binary size matter
- you need more control than Go/JavaScript typically provide
- C interop is part of the system boundary
Prefer another backend stack when:
- the team needs a richer web ecosystem immediately
- delivery speed depends on mature framework conventions
- the problem is primarily CRUD with little systems-level pressure
References
- Architecture and dependency direction: references/architecture.md
- Allocator strategy and memory ownership: references/allocators-and-memory.md
- HTTP service design and reliability: references/http-and-reliability.md
- FFI and unsafe boundary control: references/ffi-and-unsafe-boundaries.md
- Testing and debugging: references/testing-and-debugging.md
1---2name: zig-principal-engineer3description: Principal/Senior-level Zig playbook for backend services, systems-aware APIs, memory management, concurrency, performance, reliability, observability, and production operations. Use when: building or reviewing Zig services, designing low-level backend components, optimizing latency-sensitive services, hardening memory-sensitive systems, debugging allocator issues, or preparing Zig applications for production.4---56# Zig Mastery (Senior → Principal)78## Operate910- Start by confirming: Zig version, target OS/arch, deployment model, latency and memory goals, FFI requirements, concurrency model, and the definition of done.11- Prefer small vertical slices with explicit ownership, allocator strategy, and error paths.12- Keep the design boring and operable: simple boundaries, measurable behavior, and predictable cleanup beat clever abstractions.13- Treat memory, failure handling, and observability as first-class design constraints, not afterthoughts.1415> The goal is not just "fast Zig". The goal is a backend that stays correct under pressure, exposes predictable failure modes, and remains maintainable by the next engineer.1617## Default Standards1819- Keep transport, domain, and infrastructure boundaries explicit.20- Choose allocator strategy intentionally and document ownership at boundaries.21- Propagate errors with context; do not hide them behind catch-all defaults.22- Prefer explicit resource lifecycle management with `defer` and `errdefer`.23- Avoid hidden global state; inject dependencies and configuration explicitly.24- Treat outbound IO, parsing, and serialization as untrusted work: set limits, timeouts, and validation rules.25- Use the standard library first unless a dependency clearly improves correctness or delivery speed.2627## “Bad vs Good” (common production pitfalls)2829```zig30// ❌ BAD: allocator choice is implicit and cleanup is forgotten.31const data = try fetchUsers();32process(data);3334// ✅ GOOD: allocator ownership and cleanup are explicit.35var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);36defer arena.deinit();37const allocator = arena.allocator();38const data = try fetchUsers(allocator);39try process(data);40```4142```zig43// ❌ BAD: panic-style behavior for recoverable backend failures.44const body = request.reader().readAllAlloc(allocator, max_size) catch unreachable;4546// ✅ GOOD: map recoverable errors explicitly.47const body = request.reader().readAllAlloc(allocator, max_size) catch |err| switch (err) {48 error.StreamTooLong => return AppError.payload_too_large,49 else => return AppError.bad_request,50};51```5253```zig54// ❌ BAD: fire-and-forget thread with no owner or shutdown path.55_ = try std.Thread.spawn(.{}, runWorker, .{});5657// ✅ GOOD: thread lifecycle belongs to a supervisor.58const worker = try std.Thread.spawn(.{}, runWorker, .{shutdown_signal});59defer worker.join();60```6162## Workflow (Feature / Refactor / Bug)63641. Reproduce behavior or codify it with a failing test.652. Decide boundaries: protocol, orchestration, domain logic, persistence, and external integrations.663. Define allocator strategy, ownership, and cleanup rules.674. Implement the smallest end-to-end slice.685. Validate tests, formatting, release behavior, and operational guardrails.696. Review latency, allocations, failure modes, and shutdown behavior before release.7071## Validation Commands7273- Run `zig fmt .`.74- Run `zig test src/main.zig` or the relevant test targets.75- Run `zig build test` if the project uses a build graph.76- Run `zig build -Doptimize=ReleaseSafe` before release validation.77- Run smoke tests against the compiled binary in a production-like environment.78- Run container build validation if the service is deployed via Docker.7980## Backend Architecture Guardrails8182- Prefer a modular monolith before inventing service sprawl.83- Keep HTTP or TCP transport thin; map protocol concerns at the edge.84- Make timeouts, retries, backoff, and circuit breaking explicit for outbound calls.85- Bound request size, parsing depth, and connection counts.86- Treat memory pressure as an operational event: measure allocations and cap untrusted workloads.87- Every background thread or async task needs an owner, stop condition, and failure-reporting path.8889## Reliability and Operations9091- Expose `/health` and `/ready` style endpoints if the service runs behind orchestration.92- Use structured logs with request IDs and stable fields.93- Implement graceful shutdown: stop accepting traffic, drain in-flight work, release resources, and join workers.94- Prefer idempotent handlers where retries may occur.95- Benchmark hot paths before micro-optimizing.9697## Security Checklist (Minimum)9899- Validate all untrusted input lengths, counts, and encodings.100- Use allowlists for outbound destinations in sensitive environments.101- Never log secrets, credentials, or raw sensitive payloads.102- Use parameterized queries and least-privilege credentials for data stores.103- Keep unsafe FFI boundaries isolated and well-tested.104105## Decision Heuristics106107```text108Choose Zig when:109- predictable memory behavior matters110- latency and binary size matter111- you need more control than Go/JavaScript typically provide112- C interop is part of the system boundary113114Prefer another backend stack when:115- the team needs a richer web ecosystem immediately116- delivery speed depends on mature framework conventions117- the problem is primarily CRUD with little systems-level pressure118```119120## References121122- Architecture and dependency direction: [references/architecture.md](references/architecture.md)123- Allocator strategy and memory ownership: [references/allocators-and-memory.md](references/allocators-and-memory.md)124- HTTP service design and reliability: [references/http-and-reliability.md](references/http-and-reliability.md)125- FFI and unsafe boundary control: [references/ffi-and-unsafe-boundaries.md](references/ffi-and-unsafe-boundaries.md)126- Testing and debugging: [references/testing-and-debugging.md](references/testing-and-debugging.md)