Golang interview questions in 2026 concentrate on concurrency: goroutines, channels, select, the sync package, and context cancellation. Syntax and type questions show up early, but most rejections happen when a candidate writes a worker pool that leaks goroutines, closes a channel from the wrong side, or cannot explain a data race. If you can write and defend four concurrency patterns from memory, you can pass most Go coding interviews.
Below are the questions backend and infrastructure teams ask, with worked answers and code for Go 1.25 or newer.
Key Takeaways
- Concurrency is the filter. Expect at least one live exercise on a worker pool, fan-in/fan-out pipeline, or cancellable fetcher, and expect follow-ups on how every goroutine exits.
- The classic gotchas still get asked:
appendsharing a backing array, a typed nil pointer inside an interface being non-nil, and concurrent map writes crashing the program. - Know what changed recently. Go 1.22 made loop variables per-iteration, Go 1.25 added
sync.WaitGroup.Goand madeGOMAXPROCSrespect container CPU limits, and Go 1.27 added generic methods. - Run everything with
go test -race. Saying "I would verify this with the race detector" is a strong signal; actually doing it in a take-home is stronger. - Senior loops replace definitions with debugging: you get leaky or racy code and must find the bug out loud.
What Do Go Interviews Actually Test?
Go interviews test whether you can write correct concurrent code, not whether you have memorized the spec. The language is small, so interviewers move quickly past syntax and spend their time on the parts where real production bugs come from.
Here is how the rounds usually break down for backend roles. The weighting is our read of common loop formats, not published data from any company.
| Topic | Typical question | What separates pass from fail |
|---|---|---|
| Types and interfaces | How does Go satisfy an interface? What is embedding? | Explaining implicit satisfaction and the nil interface trap |
| Slices and maps | What does append do when capacity runs out? | Knowing aliasing and concurrent map write behavior |
| Goroutines and channels | Buffered vs unbuffered, who closes a channel | Clear ownership and shutdown rules |
| sync and races | Mutex vs channel, how to find a race | Using -race and picking the simplest primitive |
| Context | How do you cancel a request tree? | Passing ctx as the first argument and honoring Done() |
| Errors and generics | errors.Is vs errors.As, when to use generics | Wrapping with %w and preferring interfaces for behavior |
| Live coding | Worker pool, rate limiter, pipeline | No leaked goroutines, no deadlock on early exit |
Go-heavy companies, including Uber and Cloudflare, tend to push hardest on the bottom four rows. If you are still choosing a language for general algorithm rounds, our guide on what language to use in a coding interview covers the trade-offs.
Types, Interfaces, and Embedding
An interface in Go is a set of method signatures, and any type that has those methods satisfies it implicitly. There is no implements keyword. This is why Go code defines small interfaces at the point of use, like io.Reader, instead of large ones next to the implementation.
Q: What is the difference between a value receiver and a pointer receiver?
A value receiver gets a copy, so mutations do not affect the caller. A pointer receiver can mutate the original and avoids copying large structs. The interview follow-up is method sets: if Save has a pointer receiver, then *User satisfies an interface requiring Save, but User does not.
Q: When is an interface value not equal to nil?
An interface value holds a type and a value. It is nil only when both are nil. Returning a typed nil pointer as an error produces a non-nil interface, which is one of the most asked Go gotchas.
type MyErr struct{}
func (*MyErr) Error() string { return "boom" }
func run() error {
var e *MyErr
return e
}
func main() {
fmt.Println(run() == nil) // false
}
The fix is to return a literal nil on the success path rather than a typed nil variable.
Q: What is embedding, and is it inheritance?
Embedding places one type inside another without a field name, which promotes the inner type's fields and methods to the outer type. It is composition, not inheritance. The outer type does not become a subtype, and a promoted method's receiver is still the inner value, so there is no virtual dispatch back to the outer type.
Slices, Maps, and Memory Gotchas
A slice is a small header with three fields: a pointer to a backing array, a length, and a capacity. Most slice bugs come from forgetting that two slices can share the same backing array.
Q: What does this print?
a := make([]int, 3, 4)
b := append(a, 1)
c := append(a, 2)
fmt.Println(b[3], c[3]) // 2 2
Both appends fit within capacity 4, so they write to the same backing array and the second overwrites the first. Once capacity runs out, append copies to a new array and sharing stops. Strong candidates mention the full slice expression a[low:high:max] or slices.Clone as ways to force a copy.
Q: Why can a small slice keep a large amount of memory alive?
Reslicing a large array keeps the whole array reachable. If you read a 100 MB file and keep data[:64], the garbage collector cannot free the rest. Copy the bytes you need into a new slice.
Q: Are Go maps safe for concurrent use?
No. Concurrent writes, or a write concurrent with a read, can make the runtime abort with fatal error: concurrent map writes. This is a fatal error, not a recoverable panic. Protect the map with a sync.Mutex or sync.RWMutex, or use sync.Map for the narrow cases it is designed for, such as keys that are written once and read many times. Map iteration order is also deliberately unspecified, so never depend on it in tests.
Other quick-fire questions in this area:
nilslice vs empty slice: both have length 0 and work withappend, butjson.Marshalencodes nil asnulland empty as[].- Since Go 1.22, each loop iteration gets its own variable, so the classic "goroutines all print the last value" closure bug no longer happens in modules declaring Go 1.22 or later. Interviewers still ask about it to see if you know why it used to happen.
Goroutines, Channels, and Select
A goroutine is a function running concurrently, scheduled by the Go runtime onto a smaller number of OS threads. Goroutines start with a small stack that grows as needed, which is why programs can run hundreds of thousands of them. The number of threads executing Go code at once is controlled by GOMAXPROCS. Since Go 1.25, its default respects cgroup CPU limits in containers, a detail worth knowing for Kubernetes-heavy roles.
A channel is a typed conduit for passing values between goroutines. The rules interviewers expect you to recite without hesitation:
| Operation | nil channel | Open channel | Closed channel |
|---|---|---|---|
| Send | Blocks forever | Blocks until received (or buffer has room) | Panics |
| Receive | Blocks forever | Blocks until a value arrives | Returns zero value, ok == false |
| Close | Panics | Succeeds | Panics |
Two rules follow. Only the sender closes a channel, since a receiver closing it can crash a sender. And setting a channel to nil inside a select loop disables that case.
Q: How do you add a timeout to a channel receive?
select {
case v := <-results:
return v, nil
case <-time.After(2 * time.Second):
return 0, errors.New("timeout")
case <-ctx.Done():
return 0, ctx.Err()
}
In production, prefer a context deadline, because it propagates downstream.
Q: What is a goroutine leak?
A goroutine leak is a goroutine that blocks forever, usually on a send nobody receives or a receive nobody sends to. To find one, take a goroutine profile with net/http/pprof and look for thousands of goroutines parked on the same line.
Concurrency follow-ups are where Go interviews get uncomfortable: "what happens to that goroutine if the caller cancels?" TechScreen is an invisible AI interview assistant that helps you reason through goroutine lifecycles and channel ownership in real time, without showing up on Zoom, Google Meet, or CoderPad screen shares. Start with 3 free tokens, no credit card.
The sync Package and Race Conditions
A data race happens when two goroutines access the same memory concurrently and at least one access is a write, without synchronization. Go's memory model gives no guarantees about racy programs, so the answer to "what will this print?" for racy code is "anything."
Q: Fix this counter.
var count int
var wg sync.WaitGroup
for range 1000 {
wg.Go(func() {
count++
})
}
wg.Wait()
fmt.Println(count) // often less than 1000
count++ is a read, an add, and a write. Two goroutines can read the same value and both write back the same result. Running this with go run -race prints a WARNING: DATA RACE report showing both goroutines' stacks. Fix it with sync.Mutex, or better here, atomic.Int64:
var count atomic.Int64
var wg sync.WaitGroup
for range 1000 {
wg.Go(func() {
count.Add(1)
})
}
wg.Wait()
fmt.Println(count.Load()) // 1000
wg.Go arrived in Go 1.25 and calls Add(1) and Done() for you. If an interviewer's environment runs an older version, use the wg.Add(1) before go func() { defer wg.Done() ... }() form, and call Add before starting the goroutine, never inside it.
Per the Go race detector documentation, the detector only finds races on code that actually runs, and it adds significant memory and CPU overhead, so teams run it in tests and CI rather than in production.
Choosing a primitive is a common open question. A good answer is short:
| Need | Use |
|---|---|
| Protect shared state, like a cache map | sync.Mutex or sync.RWMutex |
| Single counter or flag | sync/atomic typed values |
| Run initialization exactly once | sync.Once or sync.OnceValue |
| Wait for a group of goroutines | sync.WaitGroup |
| Wait for a group and return the first error | errgroup.Group from golang.org/x/sync |
| Hand off ownership of data, signal events | Channels |
The Go proverb "share memory by communicating" does not mean "never use a mutex." A mutex around a map is usually simpler and faster than a goroutine that owns the map and serves requests over channels. Saying so, with the reason, reads as experience. For language-agnostic versions of these questions, see our concurrency and multithreading interview guide.
Context and Cancellation
A context.Context carries a cancellation signal, a deadline, and request-scoped values across API boundaries. Cancelling a parent cancels every context derived from it, which is how a single client disconnect stops all the database calls and HTTP requests that request started.
The conventions interviewers check:
ctxis the first parameter, namedctx, and is never stored in a struct.- Every
WithCancel,WithTimeout, orWithDeadlineis followed bydefer cancel()to release resources. - Long-running loops check
ctx.Done()in aselect. - Context values are for request-scoped data like trace IDs, not for optional function parameters.
func fetch(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
Newer APIs earn bonus points with experienced interviewers: context.WithCancelCause (Go 1.20) records why a context was cancelled, context.AfterFunc (Go 1.21) runs cleanup when a context ends, and context.WithoutCancel (Go 1.21) lets background work outlive the request that started it.
Error Handling Idioms and Generics
Errors in Go are values returned as the last result, and callers check them explicitly. The modern idioms are wrapping with fmt.Errorf("...: %w", err) to add context, errors.Is to compare against a sentinel like sql.ErrNoRows, and errors.As to extract a specific error type. Go 1.20 added errors.Join for combining errors, and Go 1.26 added the generic errors.AsType, which removes the need to declare a target variable.
Q: When should you panic? Only for programmer errors where the program cannot safely continue. An unrecovered panic in any goroutine crashes the whole process.
Q: When should you use generics instead of interfaces?
Use generics when the code is identical across types and only the type changes: containers, Map/Filter helpers, a typed cache. Use interfaces when the behavior differs per type. Generics have been in Go since 1.18, Go 1.21 added the slices, maps, and cmp packages, and the Go 1.27 release notes added generic methods, so a method can now declare its own type parameters. A one-line example that shows fluency:
func Keys[K comparable, V any](m map[K]V) []K {
out := make([]K, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
Go Concurrency Coding Exercises (Worked Examples)
These four exercises cover most live Go concurrency rounds. For each one, the interviewer is watching for the same three things: who closes each channel, how every goroutine exits on cancellation, and whether the code passes -race. Narrate those decisions as you write; our guide to thinking out loud in coding interviews covers how.
1. Bounded worker pool
Process a list of jobs with at most n concurrent workers and stop early on cancellation.
func workerPool(ctx context.Context, urls []string, n int) []Result {
jobs := make(chan string)
results := make(chan Result)
var wg sync.WaitGroup
for range n {
wg.Go(func() {
for u := range jobs {
r := process(ctx, u)
select {
case results <- r:
case <-ctx.Done():
return
}
}
})
}
go func() {
defer close(jobs)
for _, u := range urls {
select {
case jobs <- u:
case <-ctx.Done():
return
}
}
}()
go func() {
wg.Wait()
close(results)
}()
var out []Result
for r := range results {
out = append(out, r)
}
return out
}
The producer owns and closes jobs. A separate goroutine closes results only after every worker has returned, which is the step candidates most often forget. Without it, the final range results blocks forever. Every send sits in a select with ctx.Done(), so cancellation cannot strand a goroutine.
2. Fan-in (merge N channels)
func merge[T any](ctx context.Context, chans ...<-chan T) <-chan T {
out := make(chan T)
var wg sync.WaitGroup
for _, c := range chans {
wg.Go(func() {
for v := range c {
select {
case out <- v:
case <-ctx.Done():
return
}
}
})
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Fan-out is the inverse: several workers read from one channel, which is exactly what the worker pool above does. A good follow-up to raise yourself: on cancellation, the upstream producers feeding chans must also watch the same context, or they will block on their own sends.
3. Parallel fetch with first-error cancellation
When any task failing should cancel the rest, errgroup is the idiomatic tool. SetLimit bounds concurrency without a hand-built pool.
func fetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8)
bodies := make([][]byte, len(urls))
for i, u := range urls {
g.Go(func() error {
b, err := fetch(ctx, u)
if err != nil {
return err
}
bodies[i] = b
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return bodies, nil
}
Each goroutine writes its own index, so bodies has no race. Say so out loud.
4. Rate limiter or concurrent cache
The fourth common prompt is a token bucket rate limiter or a concurrent TTL cache: a struct with a sync.Mutex and a map, plus an eviction goroutine that stops on context cancellation. The same problem appears at system scale in our rate limiter system design walkthrough, and a concurrent crawler variant shows up in the web crawler design guide.
A checklist to run before saying "done"
- Does every goroutine have a guaranteed exit path?
- Is each channel closed exactly once, by its sender?
- Does every blocking send or receive also select on
ctx.Done()? - Is shared state protected, and would
go test -racepass? - Are errors propagated rather than logged and dropped?
Golang Interview Questions for Experienced Engineers
Senior and staff loops for Go roles drop the definitions and test judgment. Expect a code review of buggy concurrent code, or a design discussion grounded in Go's runtime. Questions that recur:
- How would you implement graceful shutdown for an HTTP server? (Catch the signal with
signal.NotifyContext, callserver.Shutdown(ctx)with a timeout, and drain workers.) - A service's memory grows steadily. How do you investigate? (Heap and goroutine profiles with pprof, compare snapshots over time.)
- How do you reduce allocations in a hot path? (Benchmark with
-benchmem, preallocate slices, reuse buffers withsync.Pool, check escape analysis with-gcflags=-m.) - How do you structure packages and errors across a large codebase? (Small interfaces defined by consumers, wrapped errors with context, sentinel errors only for conditions callers branch on.)
- How do you test time-dependent concurrent code? (
testing/synctest, stable since Go 1.25, runs tests in a bubble with a fake clock.)
These overlap heavily with the operational questions in our backend engineer interview guide and the SRE interview guide, so prepare them once and reuse the stories.
If your Go interview includes a live concurrency exercise on CoderPad or HackerRank, TechScreen gives you real-time help on channel ownership, cancellation paths, and race fixes while staying invisible on screen share. Try it on a mock worker pool round first with 3 free tokens, no credit card required.
Frequently Asked Questions
What are the most common Golang interview questions?
The most common Golang interview questions cover interfaces and implicit implementation, the difference between slices and arrays, how append shares backing arrays, map safety under concurrent writes, goroutines versus OS threads, buffered versus unbuffered channels, select with timeouts, sync.Mutex versus channels, context cancellation, and error wrapping with errors.Is and errors.As. Experienced candidates are usually also asked to write a worker pool or fan-in pipeline and explain how they would detect a data race.
How should I prepare for a Go concurrency interview?
Write four programs from memory until they come out bug-free: a bounded worker pool, a fan-out/fan-in pipeline, a function that respects context cancellation and timeouts, and a concurrent cache protected by a mutex. Run each with go test -race. Then practice explaining who closes each channel and how every goroutine exits. Interviewers grade goroutine lifecycle and shutdown more heavily than raw speed.
Is Go a good language for coding interviews?
Go works well for coding interviews at companies that use it, such as Uber, Cloudflare, and many infrastructure teams, and it is acceptable almost everywhere else. Its downside for algorithm rounds is verbosity: there is no built-in heap, set, or deque, so you rely on container/heap, maps of struct{}, and slices. Its upside is that concurrency questions become much easier to answer cleanly than in most other languages.
What is the difference between a buffered and an unbuffered channel in Go?
An unbuffered channel has no capacity, so a send blocks until a receiver is ready, which makes it a synchronization point between two goroutines. A buffered channel has a fixed capacity, so sends only block when the buffer is full and receives only block when it is empty. Buffers smooth out bursts, but they do not fix a design where a consumer can stop reading; that still leaks the blocked sender.
Does Go support generics in 2026?
Yes. Go has supported type parameters on functions and types since Go 1.18, with constraints such as comparable and cmp.Ordered. Go 1.24 added generic type aliases, and Go 1.27, released in August 2026, added generic methods, so a method can now declare its own type parameters. Interviewers mostly ask when generics are the right tool compared with interfaces, not about edge cases of the syntax.
How do you find a race condition in a Go program?
Run the tests or binary with the -race flag, for example go test -race ./... The race detector instruments memory accesses at runtime and reports two goroutines touching the same variable without synchronization, with stack traces for both. It only finds races on code paths that actually execute, so pair it with tests that exercise concurrent paths. Fix the race with a mutex, an atomic type, or by giving ownership of the data to a single goroutine.
What Go questions are asked for senior or experienced roles?
Senior Go interviews shift from definitions to design and debugging. Expect questions about goroutine leaks and how to find them with pprof, choosing between channels and mutexes, propagating context through a service, graceful shutdown of an HTTP server, structuring errors across package boundaries, GOMAXPROCS behavior in containers, and reducing allocations in a hot path. You will often be handed buggy concurrent code and asked to find the problem.
Ready to use AI assistance in your next interview?
TechScreen is the invisible AI assistant trusted by engineers interviewing at Google, Meta, Amazon, and hundreds of other companies. Start with 3 free tokens — no credit card required.
Ace your next interview →