GoGoroutinesContext

Go Backend Interview Questions

Go interviews are concurrency interviews. The language makes starting concurrent work trivially cheap, which means the interesting questions are all about what happens afterwards: who stops the goroutine, who closes the channel, what cancels the work when the caller gives up. A candidate who can start a thousand goroutines and cannot say how they terminate has demonstrated the problem rather than the skill.

The second theme is explicitness. Go has no exceptions, so error handling is control flow you write by hand, and interviewers look at whether you handle it deliberately or mechanically. An ignored error return is the most common thing reviewers flag in a Go machine coding round, and it is read as carelessness rather than brevity.

Written and reviewed by Sahil Srivastav

What the bar actually is

You should be able to reason about goroutine lifecycle without hand-waving: how a goroutine blocked forever on a channel send leaks, how `context` propagates cancellation down a call tree, and why starting a goroutine without a plan for its termination is a defect rather than a style choice.

On data correctness, you are expected to know that Go does not protect you from races — the race detector finds them, the compiler does not — and to reach for the right primitive. A channel to transfer ownership, a mutex to protect shared state, an atomic for a counter. Interviewers probe whether you can tell those three situations apart.

How the rounds are structured

Machine coding with concurrency built in

A worker pool, a fan-out/fan-in pipeline, or a rate limiter. Expect to be asked how it shuts down cleanly, which is where most solutions fall apart.

Channels and synchronisation

Buffered versus unbuffered semantics, who closes a channel, `select` with a cancellation case, and the classic deadlocks. The core Go round.

Context and cancellation

Timeout propagation through layers, why a context must be threaded rather than stored, and what happens to in-flight work when a request is cancelled.

Error handling and API design

Wrapping with `%w`, sentinel errors versus typed errors, and when a returned error should abort versus degrade.

What this interview bar tests

Goroutine termination

Every goroutine needs a termination path. A send on a channel nobody reads, or a receive on a channel nobody closes, leaks the goroutine and everything it references — permanently.

Channel ownership

The sender closes; receivers never do. Closing from the receiving side, or closing twice, panics. Being clear about ownership is what makes concurrent Go readable.

Context threaded, not stored

Context is a parameter, not a struct field. Storing it detaches cancellation from the call that owns it, which is how requests keep working after the client has gone.

Races the compiler misses

Concurrent map access panics; concurrent slice or struct mutation silently corrupts. `go test -race` is the tool, and candidates who mention it unprompted stand out.

A preparation plan that works

  1. 1Write a worker pool and then make it shut down correctly: cancel via context, drain in-flight work, close the results channel from the right side, and have the caller observe completion. Getting this right once teaches most of the Go round.
  2. 2Deliberately leak a goroutine — block forever on an unbuffered send — then find it with `runtime.NumGoroutine` or a pprof goroutine dump. Leaks are abstract until you have watched the count climb.
  3. 3Run `go test -race` on something with a shared map or counter and read the report. It is the fastest way to internalise which operations are unsafe.
  4. 4Practise threading a timeout through three layers and verifying the innermost call actually aborts. Many candidates pass a context and never check that cancellation arrives.

Questions you should expect

How does a goroutine leak, and how would you find one?

It blocks forever — a send on a channel with no receiver, a receive on a channel never closed, or a wait on a `WaitGroup` whose counter never reaches zero. It is never collected, and it retains everything it references. Find it with a pprof goroutine profile, which shows stacks grouped by where they are parked; a growing count at one stack is the leak.

Who should close a channel?

The sender, and only one sender. Receivers detect closure via the second return value or a `range` loop ending. Closing from the receiving side or closing twice panics, and with multiple senders you need a separate coordination mechanism — commonly a `WaitGroup` plus a single closer goroutine.

Why should context not be stored in a struct?

Because it is scoped to a single call tree and carries that call's deadline and cancellation. Stored in a long-lived struct, it either outlives its scope — so cancellation never arrives and work continues after the client has disconnected — or is shared across requests with the wrong deadline.

Mutex, channel or atomic here?

Atomic for a single counter or flag. Mutex to protect a compound invariant across several fields. Channel to transfer ownership or coordinate stages of a pipeline. Reaching for a channel to protect shared state is the common over-application, and it usually makes the code slower and harder to reason about.

What does the race detector catch that the compiler does not?

Concurrent unsynchronised access to the same memory — the compiler permits all of it. Concurrent map writes panic at runtime, which is at least loud; concurrent slice or struct field mutation corrupts silently. `-race` instruments the binary and reports the two conflicting stacks, which is usually enough to find the fix immediately.

What gets candidates rejected

  • Starting goroutines with no termination path and calling it lightweight
  • Closing a channel from the receiving side, or from multiple senders
  • Storing context in a struct so cancellation never reaches the work
  • Ignoring returned errors to keep the code short
  • Using channels to protect shared state where a mutex is simpler and faster
  • Never having run `go test -race` on concurrent code
  • Treating `sync.WaitGroup` as a completion signal while also closing the results channel from a worker

What to practise, in order

Thread pool starvation with nested tasks

Workers that submit to their own bounded pool and wait. Language-independent, and the exact failure a naive Go worker pool reproduces.

Inventory overselling under concurrency

Read-modify-write under parallel buyers, with tests asserting stock never goes negative.

Missed signal and lost wakeup in a queue

Coordination that deadlocks because a signal arrives before the waiter is waiting. Translates directly to channel and condition reasoning.

API client pagination and retry

Paging an upstream API with retries that must not duplicate or skip records — plus context deadlines that must actually abort in-flight calls.

Practise in a real repository

Gronex ships broken backend repositories with failing test suites that encode the production invariant. You read the evidence, find the defect, and make the tests pass — which is what the round actually measures, rather than whether you can recite a definition.

FAQ

Are generics asked about?

Occasionally, and usually about judgement rather than syntax: where a type parameter genuinely removes duplication versus where an interface was already sufficient. Over-generic code is penalised in Go reviews more than in most languages.

How much do I need to know about the scheduler?

Enough to explain that goroutines are multiplexed onto OS threads and that a blocking syscall does not block the whole program. Internal scheduler details are rarely probed; goroutine lifecycle always is.

Is error wrapping style actually tested?

Yes, more than candidates expect. Wrapping with `%w` so callers can use `errors.Is` and `errors.As`, and not losing context by reformatting with `%v`, is a real code-review signal in Go interviews.

Does Go experience transfer if my background is Java?

Mostly, with one adjustment: the concurrency model is explicit and compiler-unprotected. Java habits around exceptions and framework-managed lifecycles do not carry over, and interviewers will probe exactly where you are still thinking in the old model.

Related

Other preparation tracks