Top 30 Advanced Go & Rust Interview Questions and Answers
30 advanced multiple-choice Go & Rust interview questions, the deep end: internals, failure modes, and the design calls a senior engineer is expected to defend. They come from 30 bites in the Go & Rust library, the hardest slice of the 132 Go & Rust interview questions in the library. Answer them here or read straight down. Every question carries the correct option, why it is correct, and a link to the bite it came from.
Go web services, Rust backends, systems programming
30 questions. Pick an answer, or open “Show the answer” to read it.
Answers are graded in your browser. Nothing is saved, and no XP or streak is earned here. The app keeps score.
Question 1 of 30
Which statement accurately contrasts the safety responsibilities you assume when using unsafe in Rust versus Go?
Show the answer
Answer: b · Rust's borrow checker still enforces rules on safe code and only five superpowers bypass checks, while Go requires guaranteeing GC reachability and valid memory
Rust's borrow checker continues enforcing rules on safe code inside unsafe blocks; only the five superpowers bypass checks, while Go unsafe requires manual cooperation with the GC to ensure reachability and valid memory. Distractor A is wrong because the borrow checker is not fully disabled, and D is wrong because Rust still requires upholding aliasing invariants and Go unsafe enables pointer arithmetic.
Read the full bite: Contrast unsafe in Go versus Rust and the invariants you assume
Question 2 of 30
When auditing a request-rate counter that may overflow, which statement accurately distinguishes how Rust and Go handle fixed-width integer overflow by default in production release builds?
Show the answer
Answer: d · Rust panics on overflow only in debug builds and wraps in release like Go; Rust offers wrapping_ methods for explicit modular arithmetic
Rust panics on integer overflow in debug builds but wraps silently in release builds, mirroring Go's default behavior, and the wrapping_ methods explicitly opt into modular arithmetic in any mode. Option B is tempting because many candidates mistakenly believe Rust always panics, but it actually wraps in release, and saturating_ clamps to bounds rather than preventing debug panics.
Read the full bite: Default integer overflow behavior in Go versus Rust
Question 3 of 30
When designing a lookup function that may not find a value, what distinguishes Rust's Option<&T> from Go's *T at the type-system level?
Show the answer
Answer: c · Option<&T> explicitly encodes absence while &T remains non-null, costing no extra space via the null pointer optimization.
Option C is correct because Rust references are non-null by construction, and Option<&T> uses the null pointer optimization to represent None without extra memory, forcing compile-time handling of absence. Option A is a common misconception because Option is an algebraic data type with semantic guarantees that Go's implicit nullability lacks, not merely syntactic sugar.
Read the full bite: Go nil pointers vs Rust Option: impact on signatures and safety
Question 4 of 30
Which statement correctly contrasts the scope of a Go if initializer's := binding with a Rust let binding inside a block?
Show the answer
Answer: b · In Go, a variable declared with := in an if initializer is visible in both the if and else branches but not outside, while in Rust, a let binding inside a block is strictly confined to that block.
The card states that Go's if initializer creates bindings visible in every branch of that if but not outside, whereas Rust's let inside a block is confined to that block. Option C reverses these scope rules, and option D repeats the common misconception that := always mutates rather than potentially shadowing.
Read the full bite: Shadowing in Go and Rust: idioms, bugs, and if-block scoping
Question 5 of 30
In Go, if an outer struct defines a method M and embeds a type that also promotes M, what happens when M is called on the outer struct?
Show the answer
Answer: a · The outer struct's own method shadows the promoted method
Go gives precedence to methods defined directly on the outer type, so the outer struct's method shadows the promoted one. Distractor B describes the compile-time ambiguity that occurs only when two embedded fields at the same depth promote identical method names, not when the outer type defines its own.
Read the full bite: Explain Go struct embedding vs inheritance and method promotion
Question 6 of 30
On a 64-bit architecture, why can reordering struct fields from largest to smallest reduce memory usage in Go and Rust?
Show the answer
Answer: b · Compilers preserve source order and insert padding to satisfy alignment, which manual reordering minimizes.
The correct answer is C because Go and Rust maintain declared field order and insert padding bytes to meet alignment requirements; manually ordering fields by size reduces this internal padding. The most tempting distractor is A because candidates often incorrectly assume compilers automatically optimize struct layout, but the card explicitly states this is a red flag.
Read the full bite: How does struct field ordering affect memory layout in Go and Rust?
Question 7 of 30
When choosing between an enum and boxed trait objects for a heterogeneous shape collection in Rust, which statement best captures a fundamental architectural difference?
Show the answer
Answer: a · Vec<Box<dyn Draw>> stores fat pointers and scatters shape data across heap allocations, while Vec<Shape> keeps all data contiguous with static dispatch.
Vec<Shape> stores variants contiguously with static dispatch, while Vec<Box<dyn Draw>> uses fat pointers that scatter heap allocations and incur vtable indirection. The distractor claiming dyn Trait inherently requires Box is incorrect because trait objects only require indirection, which can also be provided by references.
Read the full bite: Compare enum vs trait objects for heterogeneous shapes in Rust
Question 8 of 30
In a large Rust plugin system, why does a workspace of plugin crates typically give better incremental build times than one crate with module plugins?
Show the answer
Answer: a · The crate is the compilation unit, so separate crates rebuild independently while a module change rebuilds the whole crate
Because the crate is Rust's compilation unit, changing one plugin crate recompiles only it and its dependents, whereas a module lives inside one crate that rebuilds wholesale on any change. Caching applies to crates, not the reverse of the first option.
Read the full bite: Rust workspace versus single crate for plugins
Question 9 of 30
Which statement accurately contrasts Go's escape analysis with Rust's ownership regarding garbage collection and memory placement?
Show the answer
Answer: c · Go uses escape analysis within a garbage-collected runtime to keep non-escaping locals on the stack, while Rust's ownership enforces deterministic lifetimes that remove the need for a tracing garbage collector entirely.
Go's escape analysis is a compile-time optimization that reduces heap size and GC workload, but it does not eliminate the garbage collector. Option B is wrong because Go still requires a GC for values that escape to the heap, whereas Rust's ownership system avoids tracing GC costs entirely by enforcing deterministic lifetimes at compile time.
Read the full bite: Explain Go escape analysis and Rust ownership for stack vs heap
Question 10 of 30
Which statement correctly contrasts memory reclamation for reference cycles in Rust Rc versus Go's tracing GC?
Show the answer
Answer: c · Rc leaks cyclic object graphs because internal counts never reach zero, whereas Go's tracing GC can identify and reclaim unreachable cycles.
Rc only deallocates when its reference count reaches zero, so a cycle of Rc pointers keeps counts above zero and leaks memory, while Go's tracing GC starts from roots and can mark an unreachable cycle for collection. Option A is tempting but wrong because the borrow checker does not prevent Rc reference cycles at compile time.
Read the full bite: Explain Rust Rc and Arc versus Go's tracing GC
Question 11 of 30
Which statement accurately contrasts Rust unsafe FFI and Cgo when passing a buffer to a C compression library?
Show the answer
Answer: b · In Rust, the programmer must manually guarantee pointer validity and lifetimes across the boundary, while Cgo automatically copies slices and forbids storing Go pointers in C memory.
Rust unsafe shifts the proof burden to the programmer, who must ensure pointer validity and aliasing rules for zero-cost FFI, while Cgo trades performance for safety by automatically copying data and restricting Go pointers in C memory. Option A is a common misconception: unsafe does not disable all compiler checks or Rust's underlying aliasing invariants.
Read the full bite: Rust unsafe FFI vs Cgo: who owns memory safety?
Question 12 of 30
When defining a custom struct-based error type that wraps an underlying error to add context, which implementation choice is required for errors.Is and errors.As to traverse the wrapper?
Show the answer
Answer: d · Implement an Unwrap method returning the underlying error field
errors.Is and errors.As rely on the Unwrap method to walk the chain; without it, they cannot reach the underlying sentinel even if Error surfaces its text. Simply exposing a field or stringifying with %v does not provide the unwrapping contract.
Read the full bite: Design a custom Go error type with context, Is, As, and Unwrap
Question 13 of 30
When is reaching for panic (or unwrap) appropriate in idiomatic Go and Rust?
Show the answer
Answer: b · For unrecoverable bugs or violated invariants, while expected failures use error or Result values
Both languages reserve panic for unrecoverable programmer errors and broken invariants, returning error or Result for expected failures. Using panic for routine validation or expected input errors is an anti-pattern in both.
Question 14 of 30
In Rust's crate ecosystem, what is the primary risk that the orphan rule prevents when multiple unrelated crates depend on common standard library types and traits?
Show the answer
Answer: c · Two unrelated crates defining the same foreign trait implementation for a foreign type, causing unresolvable conflicts in downstream crates that depend on both.
The orphan rule maintains global coherence by ensuring only the crate owning a trait or type can add implementations, preventing unresolvable conflicts when unrelated crates add the same foreign impl. The overlap rule governs ambiguous impls within a single crate, which is a distinct concern from cross-crate orphan violations.
Read the full bite: Describe Rust's orphan rule and its ecosystem purpose
Question 15 of 30
Why does the standard library make Item an associated type on Iterator rather than a generic parameter Iterator<Item>?
Show the answer
Answer: d · Because a type yields exactly one element type, so associated types keep next() inference unambiguous
Each iterator produces one element type, so an associated type uniquely determines Item and next() infers without annotation. The last option is backwards: generics, not associated types, would allow multiple impls.
Read the full bite: Associated types vs generic type parameters in traits
Question 16 of 30
Why might a program using Relaxed atomics for cross-thread flags pass tests on x86 yet fail on ARM?
Show the answer
Answer: b · x86's strongly-ordered hardware prevents the reorderings that Relaxed permits, masking visibility bugs that appear on ARM's weakly-ordered hardware.
Correct because the card states x86's strong hardware ordering suppresses reorderings that Relaxed allows, hiding visibility bugs that ARM's weak ordering exposes. Distractor A is tempting but wrong because x86 does not formally upgrade Relaxed to Acquire-Release semantics; relying on its hardware behavior is an unsupported portability trap.
Read the full bite: When should Rust atomics replace a Mutex, and how do orderings work?
Question 17 of 30
In a Rust workspace with a server, core library, and client crate, what specific problem does the single shared Cargo.lock primarily prevent?
Show the answer
Answer: a · Different workspace members resolving incompatible versions of the same transitive dependency, causing type mismatches across crate boundaries.
The card's concrete example states that without a shared Cargo.lock, member crates can pull in different versions of a transitive dependency like tokio, causing type mismatches when structures cross crate boundaries. The shared target directory reduces disk usage, but that is a separate feature from the lockfile's role in unifying dependency resolution.
Read the full bite: How do you manage multiple related Rust crates as a single unit?
Question 18 of 30
How does a build.rs script tell the Rust compiler to link against a native library it just compiled?
Show the answer
Answer: d · By printing cargo:rustc-link-lib and cargo:rustc-link-search directives to stdout
Cargo reads the script's stdout and acts on cargo: directives like rustc-link-lib and rustc-link-search. The script does not edit Cargo.toml or invoke rustc itself, and rustc does not auto-discover libraries by directory.
Read the full bite: Purpose and mechanism of a Rust build.rs script
Question 19 of 30
You manually add a require directive to go.mod and skip go mod tidy. Your build passes locally, but a teammate with a fresh module cache sees a security error. What explains this discrepancy?
Show the answer
Answer: c · Tidy computes the minimal build list via MVS and ensures go.sum contains checksums for every module in that list, including indirect dependencies. Without it, missing checksums cause verification failures on fresh caches.
go mod tidy computes the minimal build list using Minimal Version Selection and populates go.sum with checksums for every module, which fresh caches need for verification. Distractor A is tempting but incorrect because tidy does not upgrade dependencies to their latest versions; it only resolves the minimal versions actually imported by the code.
Read the full bite: What does go mod tidy do beyond adding dependencies?
Question 20 of 30
How do Go and Rust standard libraries differ in expressing a socket read timeout?
Show the answer
Answer: a · Go's SetReadDeadline takes an absolute time.Time while Rust's set_read_timeout takes a relative Option<Duration>
Go expresses deadlines as absolute time.Time values that compose with request budgets, whereas Rust's std takes a relative Duration per socket. The roles are not reversed and both clearly support timeouts.
Read the full bite: Network read/write timeouts in Go vs Rust stdlib
Question 21 of 30
Why must a Rust synchronous server pair its custom cancellation flag with a socket read timeout to support graceful shutdown?
Show the answer
Answer: a · Because setting the flag cannot interrupt a thread already blocked on a socket read, so a timeout lets the loop wake and re-check
Cancellation is cooperative; a blocked read ignores the flag, so a read timeout returns control to the loop to observe the flag. Rust std has no context.Context, and timeouts affect only the one socket operation.
Read the full bite: Cancellation: Go context vs Rust sync stdlib
Question 22 of 30
When hunting a memory leak in a Go service, why is inuse_space more useful than alloc_space?
Show the answer
Answer: b · inuse_space shows memory still retained after GC, so its steady growth signals a leak; alloc_space counts cumulative allocation including freed memory
inuse_space reflects currently retained memory, so rising inuse across snapshots indicates a leak; alloc_space is cumulative allocation that stays high even when memory is freed. The roles are not swapped and the metrics are not identical.
Read the full bite: Diagnosing Go memory leaks with pprof heap profiles
Question 23 of 30
Before using perf annotate to map cache-miss counters to Rust source, what build configuration is essential?
Show the answer
Answer: c · A release build with debug symbols enabled so perf can map addresses to source lines
You profile optimized release code but must keep debug symbols so perf maps sampled addresses back to functions and lines. A debug build distorts hotspots, and perf needs symbol info, not raw source access.
Question 24 of 30
Which best explains why Go's standard library provides built-in HTTP profiling endpoints while Rust's does not?
Show the answer
Answer: c · Go's runtime already maintains profiling state, so exposing it via HTTP is a low-cost side effect, whereas Rust avoids runtime overhead and leaves profiling to external tools or explicit dependencies.
Go's bundled runtime already maintains profiling counters for its scheduler and garbage collector, so exposing them via HTTP is nearly free, whereas Rust's zero-cost design pushes observability to explicit crates or OS-level tools. Option A is tempting but wrong because it claims Go's approach adds significant overhead, when in fact the runtime already incurs that cost and the HTTP layer is a minor side effect.
Read the full bite: Compare Go and Rust approaches to exposing profiling data
Question 25 of 30
In a proc-macro derive that generates a Builder pattern, what is the primary role of the quote crate in the final step?
Show the answer
Answer: a · To convert a Rust-like template into a TokenStream while preserving hygiene for generated identifiers
The card explains that quote turns Rust-like syntax back into a TokenStream and preserves hygiene so generated identifiers do not collide with user code. Option D describes syn's role, and D reflects the common misconception that derive macros can modify the original struct.
Read the full bite: Implement a custom derive macro for a Builder pattern
Question 26 of 30
What is the central risk when using unsafe to alias a Go string's bytes as a []byte without copying?
Show the answer
Answer: c · Writing through the resulting slice mutates an immutable string, which is undefined behavior
Strings are immutable and may reside in read-only memory, so writing through an aliased slice is undefined behavior that can corrupt shared strings. The whole point of the technique is to avoid the allocation, so it does not copy.
Read the full bite: Zero-copy string to []byte conversion via unsafe in Go
Question 27 of 30
Why must a safe Rust wrapper use a local size_t variable rather than a pointer to the Vec's length field when calling a C buffer-output function?
Show the answer
Answer: c · Letting C write directly into the Vec's metadata violates Rust invariants and causes undefined behavior.
The correct answer is C because Vec's length is internal metadata managed by Rust; allowing C to modify it directly breaks invariants and is undefined behavior even if the value written is correct. Option B describes a necessary validation step but does not explain why the local variable must be used instead of the Vec's length field—if C wrote directly to the field, the Vec would already be corrupted before validation could occur.
Read the full bite: Design a safe Rust wrapper taking &[i32] and returning Vec<i32>
Question 28 of 30
In a safe Rust wrapper over a C API, what is the idiomatic way to guarantee a C-owned resource is released exactly once?
Show the answer
Answer: d · Implement Drop on the owning struct so the C destructor runs automatically when the value goes out of scope
Implementing Drop gives RAII, releasing the resource deterministically once, even on panic, without caller action. A manual free invites leaks and double frees, and Rust has no garbage collector.
Read the full bite: Building a safe Rust wrapper over an unsafe C API
Question 29 of 30
In a cgo preamble, what is the difference between #cgo CFLAGS and #cgo LDFLAGS?
Show the answer
Answer: c · CFLAGS is passed to the C compiler for include paths and defines; LDFLAGS is passed to the linker for library paths and libraries
CFLAGS configures the compile step (-I, -D) so the C compiles, and LDFLAGS configures the link step (-L, -l) so symbols resolve. The first option reverses these roles, and pkg-config is a separate directive emitting both.
Read the full bite: cgo directives: CFLAGS, LDFLAGS, and pkg-config
Question 30 of 30
For an L7 proxy with a strict p99 latency SLA under heavy connection churn, which Go characteristic is the primary architectural concern?
Show the answer
Answer: a · Garbage collection pauses can spike tail latency
Go's GC introduces pauses and allocation pressure that surface as p99/p999 spikes, which is the chief tail-latency risk. Goroutines are cheap, Go handles concurrency well, and TLS termination is fully supported.
Read the full bite: Architecting an L7 proxy in Go versus Rust
Could you explain these out loud?
That is what an interview actually tests. Tezvyn gives you questions like these with what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.