Go (Golang) Interview Questions & Answers (2026)
A practical guide to the most common Go interview questions in 2026 — covering goroutines, channels, concurrency patterns, interfaces, slices, error handling, defer, garbage collection, and context, with correct code examples.
Go (Golang) has become one of the most in-demand backend languages for building cloud infrastructure, microservices, and high-throughput APIs. Companies like Google, Uber, Cloudflare, and Docker rely on Go's simplicity and first-class concurrency. In a Go interview, expect questions that probe your understanding of the language's runtime — especially goroutines, channels, and the memory model — alongside idiomatic patterns for interfaces and error handling. This guide walks through the questions interviewers ask most often in 2026, with concise answers and correct, compilable code examples you can reason about out loud.
Concurrency: Goroutines and Channels
What is a goroutine, and how does it differ from an OS thread?
A goroutine is a lightweight thread of execution managed by the Go runtime, not the operating system. Goroutines start with a small stack (around 2 KB) that grows and shrinks dynamically, so you can run hundreds of thousands of them cheaply. The Go scheduler multiplexes many goroutines onto a small number of OS threads using an M:N model (M goroutines onto N OS threads). By contrast, OS threads have fixed, larger stacks (often 1–2 MB) and are scheduled by the kernel, making them far more expensive to create and switch between. You launch a goroutine simply by prefixing a function call with the go keyword.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("goroutine %d running\n", id)
}(i) // pass i as an argument to avoid capturing the loop variable
}
wg.Wait() // block until all goroutines call Done
}What are channels, and what is the difference between buffered and unbuffered channels?
Channels are typed conduits that let goroutines communicate and synchronize by sending and receiving values. An unbuffered channel has no capacity: a send blocks until another goroutine receives, so it provides a synchronization guarantee (the send happens-before the receive completes). A buffered channel has a fixed capacity; sends succeed without blocking until the buffer is full, and receives block only when the buffer is empty. Use unbuffered channels when you need a handshake between goroutines, and buffered channels when you want to decouple producers from consumers up to a known limit. Go's guiding principle is 'do not communicate by sharing memory; instead, share memory by communicating.'
package main
import "fmt"
func main() {
jobs := make(chan int, 3) // buffered channel, capacity 3
results := make(chan int)
// worker consumes jobs and produces results
go func() {
for j := range jobs { // ranges until the channel is closed
results <- j * j
}
}()
for i := 1; i <= 3; i++ {
jobs <- i
}
close(jobs) // signal that no more jobs will be sent
for i := 0; i < 3; i++ {
fmt.Println(<-results)
}
}How does the select statement work, and when would you use it?
The select statement lets a goroutine wait on multiple channel operations simultaneously. It blocks until one of its cases can proceed; if several are ready, it chooses one at random to avoid starvation. A default case makes select non-blocking, running immediately if no other case is ready. select is the backbone of concurrency patterns like timeouts, cancellation, fan-in, and multiplexing. Combining select with a context's Done channel or time.After is the idiomatic way to implement timeouts and graceful cancellation.
Reading answers is not the same as saying them under pressure. Practice explaining goroutines and channels out loud — generate a custom Go question set with the free AI Question Generator at /tools/question-generator and rehearse your spoken answers before the real interview.
Data Structures: Slices, Arrays, and Maps
What is the difference between an array and a slice in Go?
An array has a fixed length that is part of its type — [3]int and [5]int are distinct types — and it is a value type, so assigning or passing it copies all elements. A slice is a lightweight descriptor over a contiguous section of an underlying array, consisting of three fields: a pointer to the first element, a length, and a capacity. Slices are reference-like: copying a slice header copies the pointer, so two slices can share the same backing array. Appending beyond capacity allocates a new, larger backing array and copies the elements, which is why append returns a new slice header that you must reassign.
package main
import "fmt"
func main() {
s := make([]int, 2, 4) // len=2, cap=4
fmt.Println(len(s), cap(s)) // 2 4
s = append(s, 10) // fits within capacity, same backing array
s = append(s, 20)
s = append(s, 30) // exceeds cap 4, triggers reallocation
fmt.Println(s, len(s), cap(s))
// A subslice shares the backing array until a reallocation occurs
sub := s[0:2]
sub[0] = 99
fmt.Println(s[0]) // 99 — mutation is visible through the shared array
}A common bug: keeping a small slice of a huge one keeps the entire backing array alive, preventing garbage collection. If you need only a few elements from a large buffer, copy them into a fresh slice with copy() so the large array can be freed.
Interfaces and Type System
How do interfaces work in Go, and what is a nil interface pitfall?
An interface in Go is a set of method signatures. A type satisfies an interface implicitly — there is no 'implements' keyword — simply by having the required methods. Internally, an interface value is a two-word pair: a type descriptor and a pointer to the underlying value. A subtle pitfall is that an interface is nil only when both the type and the value are nil. If you assign a typed nil pointer to an interface, the interface holds a non-nil type descriptor, so a comparison to nil returns false. This trips up error handling: returning a concrete *MyError that is nil through an error interface yields a non-nil error.
package main
import "fmt"
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }
func doWork(fail bool) error {
var e *MyError // typed nil pointer
if fail {
e = &MyError{"failed"}
}
return e // BUG: even when e is nil, the returned interface is non-nil
}
func main() {
err := doWork(false)
fmt.Println(err == nil) // false — the classic nil-interface pitfall
// Fix: return nil explicitly when there is no error
}Error Handling and Defer
How does Go handle errors, and how do wrapping and errors.Is/As work?
Go treats errors as ordinary values of the built-in error interface, returned as the last return value. Idiomatic code checks errors explicitly with if err != nil rather than using exceptions. Since Go 1.13, you can wrap an error with fmt.Errorf using the %w verb to preserve the original error in a chain. errors.Is compares an error against a sentinel target anywhere in the chain, and errors.As unwraps the chain to find an error of a specific concrete type. This lets callers make decisions on the cause without depending on brittle string matching.
What does defer do, and in what order do deferred calls run?
defer schedules a function call to run when the surrounding function returns, whether normally or via a panic. Deferred calls execute in last-in, first-out order, making defer ideal for cleanup like closing files, unlocking mutexes, or releasing connections. Crucially, a deferred function's arguments are evaluated at the point of the defer statement, not when it runs. A deferred closure can also read and modify named return values, which enables patterns like converting a recovered panic into an error return via recover().
Runtime: Garbage Collection and Context
How does Go's garbage collector work, and what is escape analysis?
Go uses a concurrent, tri-color mark-and-sweep garbage collector designed for low latency. It runs mostly concurrently with your program, using a write barrier to track pointer changes during marking, and typically keeps stop-the-world pauses in the sub-millisecond range. The GOGC environment variable (default 100) controls the tradeoff between CPU overhead and heap growth. Complementing the GC is escape analysis, a compile-time step that decides whether a variable can live on the stack or must escape to the heap. Stack allocations are cheap and freed automatically on return, so keeping allocations on the stack reduces GC pressure. You can inspect escape decisions with go build -gcflags='-m'.
What is the context package used for?
The context package carries deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines. A parent context can be derived into children with context.WithCancel, context.WithTimeout, or context.WithDeadline; cancelling the parent cancels all descendants. Long-running operations should accept a context.Context as their first parameter and watch ctx.Done() to abort promptly when the caller cancels or a timeout fires. This is essential for building responsive servers that release resources when clients disconnect.
package main
import (
"context"
"fmt"
"time"
)
func slowOp(ctx context.Context) error {
select {
case <-time.After(2 * time.Second):
return nil // work finished in time
case <-ctx.Done():
return ctx.Err() // context.DeadlineExceeded or context.Canceled
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel() // always release resources associated with the context
if err := slowOp(ctx); err != nil {
fmt.Println("operation aborted:", err) // deadline exceeded
}
}Frequently Asked Questions
Is Go a good language to learn for interviews in 2026?
Yes. Go remains a top choice for backend, cloud-native, and infrastructure roles because of its simple syntax, fast compilation, and built-in concurrency. Many companies building microservices, Kubernetes tooling, and high-scale APIs specifically hire for Go, so the language pays off in both interviews and day-to-day work.
What Go topics are most likely to come up in a technical interview?
Concurrency dominates: goroutines, channels, select, and race conditions come up in nearly every Go interview. Beyond that, expect questions on slices versus arrays, interfaces and the nil-interface trap, error wrapping, defer semantics, and the runtime — garbage collection, escape analysis, and context-based cancellation.
How can I practice Go interview questions effectively?
Combine hands-on coding with spoken rehearsal. Write small programs that exercise channels and context cancellation, then explain your design aloud as if to an interviewer. Using a voice-based AI mock interview lets you practice articulating tradeoffs under time pressure and get instant feedback, which is exactly the skill live interviews test.
Practice with AI Mock Interviews
Put your knowledge to the test with our AI interviewer.