Go Developer interview questions
100 real questions with model answers and explanations for Go Developer candidates.
See a Go Developer resume example →Practice with flashcards
Spaced repetition · Hunter Pass
Questions
A bounded worker pool runs a fixed number of workers that consume jobs from a shared channel.
- The fixed worker count bounds concurrent work and resource usage independently of the number of jobs.
- A producer sends jobs and closes the jobs channel after the final job, allowing workers that range over it to exit.
- Each worker calls Done on a WaitGroup when it exits, and a coordinator waits for all workers.
- If workers produce results, the coordinator closes the results channel only after every worker has finished sending.
Why interviewers ask this: The interviewer is checking whether the candidate understands bounded concurrency, channel lifecycle, and coordinated shutdown.
Fan-out distributes work from one input stream across multiple concurrent consumers.
- Multiple goroutines can receive from the same channel without additional synchronization around each receive.
- Each sent value is delivered to exactly one successful receiver, so workers divide the stream rather than duplicate it.
- The number of consumers controls the maximum parallelism of that stage.
- Completion order can differ from input order unless ordering is restored explicitly.
Why interviewers ask this: The interviewer is checking whether the candidate can distinguish parallel work distribution from broadcasting and reason about ordering.
Fan-in combines values from multiple input channels into one output channel.
- A common implementation starts one forwarding goroutine per input channel.
- Each forwarder ranges over its input and sends every value to the shared output.
- A WaitGroup tracks the forwarders so their independent completion can be coordinated.
- One coordinator closes the output after all forwarders finish; no individual forwarder closes it.
Why interviewers ask this: The interviewer is checking whether the candidate understands many-to-one channel composition and safe output closure.
A pipeline connects concurrent stages so each stage receives values, transforms them, and sends results onward.
- Stages expose channels with compatible element and direction types, which lets one stage feed the next.
- With unbuffered channels, a slow downstream stage immediately blocks upstream sends.
- A finite buffer absorbs a limited mismatch in rates, but upstream still blocks when the buffer becomes full.
- This blocking propagates toward the source and provides backpressure without an unbounded queue.
Why interviewers ask this: The interviewer is checking whether the candidate understands pipeline composition and channel-based flow control.
Every pipeline stage must observe a shared cancellation signal while it may block.
- The downstream consumer cancels a context or closes a shared done channel when it stops before draining the pipeline.
- Each stage selects between cancellation and blocking channel operations, especially sends to downstream stages.
- On cancellation, stages return, close any output channels they own, and release other resources.
- Merely stopping the final receive loop is insufficient because upstream goroutines can remain blocked forever on sends.
Why interviewers ask this: The interviewer is checking whether the candidate can make all pipeline goroutines terminate when normal draining does not occur.
Channel ownership is the convention that one component controls sending and lifecycle decisions for a channel.
- The owner usually creates the channel, performs or coordinates all sends, and exposes a receive-only view to consumers.
- A channel is closed by the sending side only when no more values will ever be sent.
- Receivers should not close a channel, and a channel must not be closed more than once.
- With multiple senders, a separate coordinator closes the channel only after all senders have finished.
Why interviewers ask this: The interviewer is checking whether the candidate can assign channel lifecycle responsibility and avoid close-related panics.
Directional channel types restrict which channel operations code may perform.
- chan<- T permits sending and closing but rejects receives at compile time.
- <-chan T permits receiving but rejects sends and closing at compile time.
- A bidirectional chan T can be assigned or passed as either directional type, but the restricted view cannot be widened back implicitly.
- Function signatures use these types to document ownership intent and enforce API boundaries.
Why interviewers ask this: The interviewer is checking whether the candidate understands compile-time channel capabilities and their role in API design.
A nil channel is never ready for either communication direction.
- A direct send to or receive from a nil channel blocks forever.
- In a select, a case using a nil channel is disabled and cannot be chosen.
- Assigning nil to a channel variable can dynamically disable its select case without changing the select structure.
- A select whose communication cases are all disabled blocks forever unless it has a default case.
Why interviewers ask this: The interviewer is checking whether the candidate can reason precisely about nil channels and dynamic select behavior.
Select chooses one operation that can proceed at the moment the statement is evaluated.
- If exactly one case is ready, that case is selected.
- If several cases are ready, one is chosen using a uniform pseudo-random selection, with no source-order priority.
- A default case runs only when no communication case is immediately ready, making the select nonblocking.
- Repeating a default select in a tight loop can busy-spin and consume CPU.
Why interviewers ask this: The interviewer is checking whether the candidate understands readiness, fairness assumptions, and nonblocking select behavior.
Channel capacity determines how tightly senders and receivers synchronize.
- An unbuffered send completes only when a receiver participates, creating a direct handoff between the goroutines.
- A buffered send can complete without an immediate receiver while free capacity remains.
- Once the buffer is full, further sends block, so capacity sets the amount of temporary slack before backpressure begins.
- Buffering changes timing and throughput characteristics but does not remove the need for lifecycle and cancellation coordination.
Why interviewers ask this: The interviewer is checking whether the candidate understands channel capacity as a synchronization boundary rather than merely a performance option.
sync.Mutex provides exclusive access to a critical section.
- Its zero value is an unlocked mutex, Lock waits until the mutex is available, and Unlock may be called by a different goroutine.
- A successful Unlock synchronizes before a later successful Lock, so writes in the critical section are visible after that Lock.
- A mutex is not reentrant: locking it again in the same goroutine without an intervening Unlock deadlocks.
- A Mutex must not be copied after first use because the copy has distinct lock state that no longer coordinates all access to the protected data.
Why interviewers ask this: The interviewer is checking whether the candidate understands mutex identity, balanced locking, visibility guarantees, and the prohibition on copying after use.
sync.RWMutex permits either multiple readers or one exclusive writer.
- RLock can coexist with other read locks, while Lock waits for all readers and excludes both readers and writers.
- Once a writer is waiting, new RLock calls block so that a continuous stream of readers cannot starve the writer.
- It does not support upgrading an RLock to Lock or downgrading Lock to RLock, and it must not be used for recursive read locking.
- Its extra bookkeeping is useful mainly for read-heavy workloads with sufficiently long critical sections; otherwise a Mutex can be simpler and faster.
Why interviewers ask this: RWMutex improves concurrency only when parallel reads outweigh its higher coordination cost and stricter locking rules.
sync.WaitGroup waits until a tracked set of tasks has finished.
- Call Add before starting each task, and have that task call Done exactly once, commonly with defer.
- Wait blocks until the counter reaches zero, and Add panics if an update makes the counter negative.
- A positive Add performed while the counter is zero must happen before Wait; racing that Add with Wait is unsafe because Wait may return too early.
- A WaitGroup may be reused only after the previous Wait has returned, with all Add calls for the next cycle occurring afterward.
Why interviewers ask this: The counter must describe all pending work before any waiter is allowed to observe completion.
These helpers perform lazy initialization at most once and publish its completed result safely.
- Once.Do invokes its function on the first call, blocks concurrent callers until that call finishes, and never invokes it again.
- If a Once.Do function panics, that invocation still counts as completed, so later Do calls return without rerunning it.
- OnceValue caches one return value, while OnceValues caches two, and each wrapper returns the same cached result on every call.
- If a OnceValue or OnceValues initializer panics, every call to the wrapper panics with the same panic value rather than silently returning.
Why interviewers ask this: The value helpers preserve a failed initialization for every caller, whereas Once.Do suppresses later execution after the first panic.
Atomic operations make a single supported memory operation indivisible without defining a critical section.
- Loads, stores, swaps, adds, and successful compare-and-swap operations are atomic only for the addressed value and operation.
- A read-modify-write sequence made from separate atomic calls is not one atomic transaction unless it is implemented as a correct CAS loop.
- In Go, atomic operations behave as if all of them execute in one sequentially consistent order, which provides synchronization between goroutines.
- Use a mutex when an invariant spans multiple fields or several steps; use atomics for simple counters, flags, or carefully designed lock-free state.
Why interviewers ask this: Choosing atomics requires proving correctness at the exact state boundary that each atomic operation protects.
Derived contexts form a tree rooted in an existing parent context.
- Background and TODO are non-nil root contexts, while WithCancel, WithDeadline, and WithTimeout create children.
- Canceling a parent cancels every descendant derived from it.
- Canceling a child does not cancel its parent, siblings, or other branches of the tree.
- A Context is safe for concurrent use and is normally passed explicitly as the first parameter across call boundaries.
Why interviewers ask this: The tree model lets one operation stop all of its subordinate work without affecting unrelated work.
Context cancellation is cooperative: code must observe the signal and stop its own work.
- Done returns a receive-only channel that is closed when the context is canceled or its deadline expires, and it may be nil for contexts that cannot be canceled.
- Err returns nil before Done is closed, then returns either context.Canceled or context.DeadlineExceeded.
- Blocking code commonly selects between its normal channel operation and ctx.Done() to avoid waiting indefinitely.
- After observing Done, code should return promptly and release owned resources rather than expecting the context to terminate the goroutine.
Why interviewers ask this: Done supplies the signal, Err supplies the category, and the receiving code remains responsible for stopping.
WithDeadline and WithTimeout derive a context that is canceled no later than the requested time.
- WithDeadline uses an absolute time, while WithTimeout computes a deadline relative to the current time.
- The effective deadline cannot extend beyond an earlier parent deadline.
- The returned cancel function should normally be deferred immediately, even when automatic expiration is expected.
- Calling cancel early releases the derived context's timer and parent linkage and prevents resources from being retained until the deadline.
Why interviewers ask this: A deadline bounds work, while the explicit cancel function provides deterministic resource cleanup.
Context values are for request-scoped data that must cross API and process boundaries.
- Keys should use a private, comparable type rather than built-in strings to avoid collisions between packages.
- Stored values should be safe for concurrent access and should usually be exposed through typed accessor functions.
- Value searches the current context and then its ancestors, returning the nearest value for the matching key or nil.
- Configuration and optional function parameters belong in explicit arguments or option types because context values hide dependencies and weaken type checking.
Why interviewers ask this: Restricting values to cross-cutting request metadata keeps APIs visible, typed, and maintainable.
WithCancelCause lets cancellation carry a domain-specific error in addition to the standard context state.
- It returns a CancelCauseFunc that records a supplied non-nil error if the context is not already canceled; passing nil records context.Canceled.
- When CancelCauseFunc wins, Cause returns the recorded error while Err returns context.Canceled; an earlier parent cancellation retains the parent's Err and cause.
- The cause propagates to descendants unless a descendant establishes its own cancellation cause first.
- Before cancellation Cause returns nil, and after cancellation without an explicit cause it returns the same standard error as Err.
Why interviewers ask this: Cancellation causes retain diagnostic meaning without changing the stable contract of Context.Err.
Locked questions
- 21
How do the method sets of T and *T differ, and how does that affect interface satisfaction?
typesinterfaces - 22
What are interface{} and any in Go?
typesinterfaces - 23
How do type assertions to concrete and interface types work, including the comma-ok form?
typesinterfacesforms - 24
What are the semantics of a type switch in Go?
- 25
How does interface embedding provide interface composition in Go?
typesoopinterfaces - 26
What happens when embedded interfaces contain duplicate method names, and how are their type-set requirements combined?
typesinterfaces - 27
Why are Go interfaces commonly defined by consumers and kept small?
typesinterfaces - 28
What is the difference between a nil interface and an interface holding a typed nil value?
typesinterfaces - 29
How do generic functions and generic types use type parameters and instantiation in Go?
generics - 30
How does an interface act as a generic constraint by describing a permitted type set?
genericstypesinterfaces - 31
How do the any and comparable constraints differ in Go generics?
generics - 32
What does an approximation element such as ~T mean in a Go constraint?
- 33
How do union type terms work in Go constraints, and what restrictions apply to them?
uniontypes - 34
How does generic type inference work in Go, and when are explicit type arguments still needed?
generics - 35
When should Go generics be preferred over interfaces, and when are interfaces the better abstraction?
genericstypesoop - 36
What do happens-before and data race mean in the Go memory model?
memory - 37
Which channel operations create synchronization edges in the Go memory model?
memoryconcurrency - 38
What synchronization guarantees do Mutex, RWMutex, and Once provide in the Go memory model?
concurrencymemory - 39
How does Go's tracing garbage collector determine what to reclaim, including cyclic data?
gc - 40
What roles do GOGC and the soft memory limit GOMEMLIMIT play in Go's garbage collector?
gcmemory - 41
How do a slice header, its backing array, aliasing, and append reallocation relate to one another?
slices-maps - 42
What should Go code assume about slice capacity growth and the memory retained by a small subslice?
capacitymemoryslices-maps - 43
What conceptual properties of Go maps follow from their hash-table design?
design - 44
Which concurrent accesses to a regular Go map are permitted, and when is synchronization required?
concurrency - 45
How should sentinel errors be defined and tested with errors.Is?
error-handling - 46
How does errors.As work with custom error types, especially when pointer and value forms differ?
error-handling - 47
What semantics do fmt.Errorf with %w and errors.Join give to an error chain or tree?
joins - 48
Why are table-driven tests and subtests commonly combined in Go?
testing - 49
What lifecycle rules matter for t.Helper, t.Cleanup, t.Parallel, and loop variables in current Go tests?
testing - 50
How should a Go benchmark use b.N or b.Loop, timer controls, and allocation reporting?
- 51
A Go service stops responding, but CPU usage stays low. How would you investigate a possible deadlock?
locking - 52
The race detector reports two conflicting accesses in an integration test. How would you turn the report into a fix?
integration - 53
The number of goroutines grows after every failed downstream request. How would you find and fix the leak?
concurrency - 54
How would you build a concurrent pipeline where the first worker error cancels the remaining work?
ci-cdconcurrency - 55
A handler holds a mutex while calling a slow database query, causing latency spikes. How would you redesign it?
databasequeriesconcurrency - 56
Two methods acquire account and ledger locks in different orders and occasionally deadlock. How would you fix the design?
lockingdesign - 57
Several producers send to one channel, and shutdown sometimes causes send-on-closed-channel panics. How would you organize ownership?
concurrencyerror-handlingownership - 58
A batch job starts one goroutine per record and exhausts memory on large inputs. How would you add bounded concurrency?
concurrencybatchmemory - 59
A WaitGroup occasionally panics with misuse during a dynamic task crawl. How would you structure the crawl safely?
concurrencystructserror-handling - 60
A select loop processes a constantly ready data channel and reacts slowly to cancellation. How would you improve shutdown responsiveness?
reactresponsiveconcurrency - 61
A read-heavy configuration map is reloaded periodically while handlers access it. How would you make access safe and efficient?
config - 62
An initialization function uses sync.Once, but a transient failure means it can never retry. How would you change it?
resilienceconcurrency - 63
A cache uses sync.Map for all data but performs slower than expected. How would you decide whether to replace it?
cachingconcurrency - 64
A Go service suddenly uses twice as much CPU. How would you investigate it with pprof?
profiling - 65
Heap usage keeps rising in a long-running service. How would you distinguish retained memory from temporary allocation pressure?
memorydata-structures - 66
A JSON endpoint allocates heavily. How would you measure and reduce allocations without guessing?
endpoints - 67
How would you use escape analysis when a hot function allocates unexpectedly?
memory - 68
A parser repeatedly converts between string and []byte. How would you decide whether to optimize it?
optimization - 69
When would you preallocate a slice or map in production code, and how would you verify the benefit?
slices-maps - 70
pprof shows fmt.Sprintf high in a logging hot path. How would you optimize it?
loggingprofilingoptimization - 71
Latency rises with traffic, and a mutex profile points to one shared lock. What would you do next?
concurrencylatency - 72
Requests spend time blocked even though CPU and mutex contention are low. How would a block profile help?
concurrency - 73
CPU and heap profiles look normal, but request latency has periodic spikes. How would you use go tool trace?
latencydata-structures - 74
A microbenchmark shows a 30 percent speedup, but production latency does not change. How would you reassess the optimization?
optimizationlatency - 75
Would you add sync.Pool to reduce buffer allocations in a busy service?
memoryconcurrency - 76
How would you implement graceful shutdown for an HTTP service running in Kubernetes?
lifecycleserviceshttp - 77
How would you choose the order of HTTP middleware for recovery, tracing, authentication, and logging?
authhttpmiddleware - 78
How would you propagate context through an HTTP handler that calls a database and another service?
databasehttpconcurrency - 79
What would you store in context, and what would you keep as explicit function parameters?
concurrency - 80
How would you configure timeouts for a public net/http server?
resiliencehttpconfig - 81
How would you implement common authentication and observability behavior across gRPC methods?
authgrpcobservability - 82
How would you design liveness and readiness checks for a Go microservice?
microservicesdesignhealth-checks - 83
How would you add structured logging and tracing without losing request correlation?
correlationloggingstructs - 84
How would you add retries to an outbound Go client without causing a retry storm?
resilience - 85
How would you introduce a circuit breaker around an unstable downstream service?
resilience - 86
How would you ensure a database transaction stops when the request is canceled?
databasetransactions - 87
How would you shut down a Kafka consumer without losing ownership or processing messages twice unnecessarily?
ownershipkafkaconcurrency - 88
How would you add context to an error while keeping errors.Is checks working?
concurrencyerror-handling - 89
When would you use a custom error type and errors.As in a service?
error-handling - 90
How would you translate domain errors into HTTP or gRPC responses without coupling the domain to transport code?
httpgrpcmicroservices - 91
A function can fail during its main operation and again while closing a resource. How would you preserve both errors?
- 92
A batch operation succeeds for some items and fails for others. How would you design its Go result?
designbatch - 93
Where would you recover from panics in a Go service, and what would you do after recovery?
error-handling - 94
How would you handle context cancellation errors differently from ordinary service failures?
resilienceconcurrency - 95
How would you structure table-driven tests for a parser with valid and invalid inputs?
structs - 96
How would you mock a payment dependency in Go without generating a huge interface?
typesinterfacesmocking - 97
How would you test an HTTP handler including middleware and error mapping?
middlewarehttp - 98
How would you write a deterministic test for a concurrency bug instead of adding time.Sleep?
concurrency - 99
A test for context timeout behavior is flaky in CI. How would you make it reliable?
resilienceflakyconcurrency - 100
How would you design a test strategy for a Go service that uses PostgreSQL and an external gRPC API?
designmicroservicesapi