Lesson 30 · Senior Go backend engineering
Senior Go interview questions
High-signal backend and runtime questions, with the best answer shape for each.
Your win: answer common senior Go backend interview questions in a way that sounds like an engineer who has actually shipped, debugged, and tuned services — not just someone who memorised a list of facts.
Why interview answers go wrong
Many answers fail because they stop too early. “Use a mutex for shared state” is true, but incomplete. A stronger answer says why, says what the alternative is, and says what kind of bug or runtime cost appears if you choose badly.
Question bank
| Question | Best answer shape |
|---|---|
| When do you use a pointer receiver? | When you need mutation, want to avoid copying a larger struct, need method-set consistency, or want nil to have meaning. Not “always for performance.” |
Why can err != nil be true when the concrete pointer is nil? | An interface is nil only if both dynamic type and value are nil. A typed nil pointer inside an interface still makes the interface non-nil. |
| When would you use a mutex instead of a channel? | Use a mutex for protecting shared state; use channels for coordination or ownership transfer. In Go, choose the simpler mental model. |
| What is escape analysis and why do you care? | The compiler decides stack vs heap based on lifetime. Heap allocations increase GC work, so allocation behaviour matters on hot paths. |
What does GOMEMLIMIT do? | It gives the runtime a soft memory budget, especially useful in containers, so the GC can work harder before the process is OOM-killed. |
Why is errgroup often better than WaitGroup? | Because real operations need error propagation, cancellation, and often concurrency limits — not just a final wait. |
| Why can a small slice cause a memory problem? | Because the slice header can keep a huge backing array alive. The live memory is the array, not just the visible length. |
| What makes retries dangerous? | They can amplify load during partial outages. Safe retries need bounded time, bounded concurrency, cancellation, and awareness of side effects. |
| Why keep transport errors at the edge? | Because domain/application code should stay reusable. Controllers map domain failures into gRPC or HTTP semantics. |
When would you consider sync.Pool? | After measuring allocation churn on a hot path, for temporary reusable objects only. It is not a general cache. |
| Why should context not live on a struct? | Context is per-operation state. Storing it on a struct creates lifecycle mismatch and stale cancellation bugs. |
| How do you talk about goroutines being “cheap” without overselling it? | They are lightweight and runtime-scheduled, but not free; unlimited goroutines still mean more scheduling, memory, and failure-handling complexity. |
Use the official docs as your answer backbone
For high-trust prep, anchor your language to the official memory model, GC guide, context article, and Effective Go rather than random interview blogs.
→ go.dev/ref/mem · go.dev/doc/gc-guide
→ go.dev/blog/context · go.dev/doc/effective_go
Check yourself (from memory)
Q1. A strong senior answer usually includes…
Q2. The best answer to “channel or mutex?” is usually…
Sources. Go Memory Model; GC Guide; Context article; Effective Go.