Golang interview questions for experienced developers (3, 5 and 8+ years)
The questions that come after the basics: how Go behaves in production, grouped by experience level, each with a short answer and the follow-up interviewers ask next.
By devskills editorial team · Last updated
With a few years of Go, interviews stop asking what a slice is and start asking what happens when things go wrong: under load, in a container, after a deploy. This page groups those questions by the level they are usually asked at. Each answer is what you would say out loud; the follow-up is the next question, which is where experienced candidates are told apart.
If any fundamentals feel shaky, start with the 60 Golang interview questions and answers, which include eight short production questions. This page goes further and does not repeat them.
| Experience | What they are checking | Typical round |
|---|---|---|
| ~3 years | You write correct concurrent code and know the language's sharp edges | Code reading, small live coding, concurrency questions |
| ~5 years | You understand the runtime well enough to debug and tune it | Runtime internals, profiling, service design in Go |
| 8+ years | You make decisions other people live with: structure, APIs, operations | Design discussion, code review, incident stories |
Go interview questions for 3 years of experience
- A function appends to a slice it was given. Does the caller see the new element? No. The function got a copy of the slice header, so the caller's length does not change. If there was spare capacity, the caller's backing array was written to, which is worse: a hidden side effect. Follow-up: how do you design the API? Return the new slice, as
appenddoes. → Arrays and slices - Your function returns `error` and the caller sees a non-nil error, but you returned a nil pointer. Why? The function returned a typed nil (
var e *MyErr; return e), and an interface holding a typed nil is not nil. Follow-up: how do you prevent it? Return the literalnilon success, and declare the return type aserror, never as a concrete error pointer. → Interfaces - How does cancellation reach a goroutine three calls deep? Only if every function passes the same
ctxdown and every blocking call takes it or selects onctx.Done(). Nothing interrupts a goroutine from outside. Follow-up: how do you start work that must outlive the request, like an audit write?context.WithoutCancel(ctx)(Go 1.21) keeps the values but drops the cancellation. → Context - Why does `go vet` complain about a `context.WithTimeout` without calling `cancel`? The lostcancel check: until the timeout fires, the context and its timer are kept alive. Always
defer cancel(). Follow-up: does cancel stop the goroutines? No, it closesDone(); they must be watching it. - You put `defer f.Close()` inside a loop over 10,000 files. What happens? Every defer runs when the function returns, not at the end of each iteration, so you hold 10,000 open files and may hit the descriptor limit. Move the body into its own function. → Defer
- Why does `m[k].Count++` not compile when `m` is `map[string]Stats`? Map values are not addressable, because the map may move them when it grows. Copy, change and store back, or use
map[string]*Stats. → Maps - How do you test code that uses timers and sleeps without making the test slow? Inject a clock, or use
testing/synctest(standard since Go 1.25), which runs the test in a bubble with a fake clock that advances when every goroutine is blocked. Follow-up: how do you catch races in tests?go test -race, and run it in CI. → Testing - How do you return several errors from one function?
errors.Join(Go 1.20), anderrors.Isanderrors.Assee through it. A singlefmt.Errorfcan also wrap more than one error with several%wverbs. → Error values - What is the risk of embedding an interface in a struct? The struct satisfies the interface even if it implements none of the methods, and calling one panics with a nil pointer dereference if the embedded field was never set. It is useful for wrapping, dangerous by accident. → Methods and embedding
- How would you run 1,000 HTTP calls with at most 20 at a time? A worker pool of 20 or
errgroupwithSetLimit(20), a shared context so the first fatal error cancels the rest, and results written by index. → Worker pool
Go interview questions for 5 years of experience
- What is escape analysis, and how do you see it? The compiler decides whether each value can live on the stack or must move to the heap, because it is still referenced after the function returns.
go build -gcflags=-mprints the decisions ("moved to heap", "escapes to heap"). Returning a pointer to a local is legal; it just escapes. Follow-up: why care? Heap allocations cost GC work. Common causes are storing a value in an interface, closures that capture variables, and slices whose size is not known at compile time. - How does Go's garbage collector work? A concurrent, non-generational, non-moving mark-and-sweep collector, using tri-colour marking with a write barrier so it can run alongside your goroutines with short stop-the-world pauses.
GOGC(default 100) sets how much the heap may grow over the live heap before the next cycle;GOMEMLIMIT(Go 1.19) adds a soft memory ceiling. Follow-up: how do you cut GC cost? Allocate less, first; tune second. → Go GC guide - What does the Go memory model guarantee? Only what a happens-before edge gives you: a channel send before the matching receive completes, an
Unlockbefore the nextLock, async.Oncefunction before anyDoreturns, atomics in a sequentially consistent order. Without one of those, a goroutine may never see another's write. Follow-up: is a "benign" data race acceptable? No. A racy program has no guarantees, and the race detector will flag it. → The Go memory model - Explain the scheduler. G (goroutines), M (OS threads), P (processors,
GOMAXPROCSof them). Each P has a run queue, idle Ps steal work, a goroutine blocked in a syscall hands its P to another M, network waits park in the netpoller instead of holding a thread, and since Go 1.14 long-running goroutines are preempted asynchronously. Follow-up: why can heavy cgo use need many threads? A goroutine inside a C call holds its M for the whole call. → Goroutines vs threads - Which pprof profiles do you use, and for what? CPU for where time goes; heap with
inuse_spacefor what holds memory now andalloc_spacefor what allocates most; goroutine for leaks and stuck goroutines; block and mutex for contention, which are off until you setruntime.SetBlockProfileRateandruntime.SetMutexProfileFraction.go tool tracefor latency that profiles average away. Follow-up: what is PGO? Since Go 1.21 a CPU profile saved asdefault.pgoin the main package directory lets the compiler optimise hot paths, typically by a few percent. → Profile and ship - How do you shut down a service that has an HTTP server and background workers?
signal.NotifyContextfor SIGTERM, thensrv.Shutdown(ctx)with a deadline so in-flight requests finish, then stop feeding the workers, let them finish or cancel them, and wait for them before exiting. All inside the platform's grace period. → Middleware and shutdown - Why is the default `http.Client` dangerous in production? It has no timeout, so a slow upstream holds your goroutine forever. Set
Timeoutor use request contexts with deadlines. Also: the default transport keeps only 2 idle connections per host (DefaultMaxIdleConnsPerHost), and you must read and close response bodies or connections are not reused. Follow-up: how would you see the effect? Connection churn and manyTIME_WAITsockets under load. - Where should interfaces be defined? In the package that uses them, small, describing only what that caller needs. Return concrete types from constructors. Follow-up: what about mocks? A consumer-side interface of one or two methods is easy to fake by hand; a 30-method interface created only to mock a struct is a design smell. → Interfaces
- How do errors flow through the layers of a service? Each layer wraps with what it was doing (
fmt.Errorf("load order %d: %w", id, err)), the domain defines sentinel or typed errors for cases callers branch on, and only the edge decides on an HTTP status and logs. Log or return, not both, or every failure is logged five times. → Errors and logging - When does `sync.Pool` help, and when does it hurt? It helps on hot paths that allocate the same temporary buffer per request, once a profile shows allocation cost. It hurts when objects are large and rarely reused, when they are not reset, or when it is used to hold state, because the pool can drop anything at any time. → The sync package
- Are generics as fast as hand-written code? Not always. The compiler shares one instantiation per GC shape (all pointer types share one, for example) and passes a dictionary for type-specific operations, so method calls through a type parameter can be slower than direct calls. For hot paths, benchmark. Follow-up: then why use them? For containers and algorithms where the alternative is
anyand type assertions. → Generics
Go interview questions for 8+ years of experience
- How do you structure a large Go codebase? Packages by domain, not by layer ("order", not "models" and "utils");
internal/for what other modules must not import; dependencies pointing inwards to the domain; and no package namedcommon. Follow-up: several modules in one repo? Only when they release on separate schedules;go workhelps locally, and every extra module is a versioning cost. → Project layouts - How do Go modules choose versions? Minimal version selection: the build uses the minimum version that satisfies every requirement, not the newest available, which makes builds reproducible without a lock file. Major versions 2 and up change the import path (
/v2). Follow-up: you published a broken release? Add aretractdirective in a new version; never delete a tag, because the module proxy andgo.sumfiles have already recorded it. → Modules and tooling - A Go service has p99 spikes every few minutes. How do you find the cause? Correlate the spikes with GC cycles, CPU throttling and deploys in metrics first. Then take an execution trace across a spike:
go tool traceshows GC pauses, goroutines waiting on locks and scheduler delays directly. Go 1.25'sruntime/traceflight recorder keeps the last few seconds in memory, so you can save a trace right after a spike is detected. Follow-up: CPU throttling in Kubernetes? CheckGOMAXPROCSagainst the CPU limit; since Go 1.25 the runtime follows the cgroup limit by default on Linux. - How do you keep a library backward compatible? Follow the Go 1 compatibility idea in your own API: add, do not change. New behaviour behind new functions or options, old behaviour kept. When even the Go team changes behaviour, it ships a GODEBUG setting so programs can keep the old one for a while. Follow-up: when do you make a v2? When the old API is actively harmful, accepting that every caller has to change their imports.
- What do you look for first in a Go code review? Who stops every goroutine; who closes every channel;
ctxas the first parameter and actually used; errors wrapped, not swallowed or double-logged; locks held over I/O; and tests that run under-race. Style is gofmt's job. - How would you design a rate-limited, idempotent message consumer in Go? A bounded worker pool reading from the queue, a per-tenant limiter (
golang.org/x/time/rate), an idempotency key checked in the same transaction as the side effect, and acknowledgement only after commit. The system design side is idempotency and the distributed message queue problem. - When would you not choose Go? When the team and libraries are elsewhere (data science in Python), when you need fine control over memory layout and no GC pauses at all (Rust or C++), or for a large UI. Saying where Go is the wrong tool is part of a senior answer. → Interview: backend and design
- Tell me about a production incident in a Go service. Interviewers want the shape: what you saw, how you narrowed it down (metrics, then profiles or traces), the root cause in one sentence, the fix, and what you changed so it cannot recur (a test, an alert, a lint). Prepare two such stories, one about concurrency (a leak, a deadlock, a race) and one about resources (memory, connections, CPU limits).
What does escape analysis output look like?
A small example of the first 5-year question. newUser returns a pointer, so u must outlive the call and moves to the heap. sum keeps everything on the stack. Run go build -gcflags=-m on it to see the compiler's reasoning for your Go version.
package mainimport "fmt"type user struct{ name string }func newUser(name string) *user { u := user{name: name} // compiler: moved to heap: u return &u}func sum(xs [4]int) int { total := 0 // stays on the stack for _, x := range xs { total += x } return total}func main() { u := newUser("ravi") fmt.Println(u.name, sum([4]int{1, 2, 3, 4}))}- Answer runtime questions (scheduler, GC, escape analysis, memory model) in two or three sentences each.
- Explain how you would find a latency or memory problem in a running Go service.
- Defend design choices in Go code: package layout, interfaces, errors and module versions.
- At 3 years: correct concurrency and the language's sharp edges (slices, nil interfaces, defer, context).
- At 5 years: the runtime. Escape analysis, GC with GOGC and GOMEMLIMIT, the memory model, GMP, and pprof.
- At 8+ years: decisions. Package structure, module versioning, compatibility, incidents and code review.
- Every answer has a follow-up. Prepare the second sentence, not just the first.
- Bring two incident stories with a clear cause and a change that prevents a repeat.
- 1Run
go build -gcflags=-monescape.go. Which lines move a value to the heap, and why? - 2Explain, in under a minute, how you would find out why a Go service's memory grows by 200 MB a day.
- 3Write down your two incident stories in five lines each: symptom, diagnosis, cause, fix, prevention.