gitGood.dev
Back to Blog

Top 50 Go Interview Questions in 2026 (With Answers and Code)

P
Patrick Wilson
12 min read

Top 50 Go Interview Questions in 2026 (With Answers and Code)

Go has won the infrastructure layer. Every cloud-native company you've heard of uses it somewhere - Kubernetes, Terraform, Docker, CockroachDB, Tailscale, HashiCorp's whole stack. If you're interviewing for backend, platform, or SRE roles in 2026, you'll see Go on the JD even if it's not the primary language.

These 50 questions are the ones that actually came up in interviews this year - covering concurrency, the memory model, generics (now fully mainstream after years of debate), and systems design. Skip the trivia, focus on the patterns.


Language Fundamentals (1-10)

1. What's the difference between a slice and an array in Go?

Arrays have a fixed size that's part of the type ([5]int is a different type from [6]int). Slices are dynamic views over an underlying array - they hold a pointer, length, and capacity.

var arr [3]int = [3]int{1, 2, 3}
var s []int = []int{1, 2, 3}

// Slices are passed by value, but the value contains a pointer.
// Modifications to elements are visible to callers.

2. How does append work and when does it allocate?

append grows the underlying array when capacity is exceeded. Growth strategy: double until ~1024 elements, then grow by ~25%. Allocations cause the returned slice to point at new memory - always assign the result.

3. What's a nil slice vs an empty slice?

var a []int        // nil slice, len 0, cap 0
b := []int{}       // empty, non-nil, len 0, cap 0

Both are safe to range over and append to. JSON marshaling differs: a nil slice marshals to null, an empty slice marshals to [].

4. Explain the difference between make and new.

new(T) returns *T pointing to a zero value. make is only for slices, maps, and channels - it returns the initialized value, not a pointer. Use make when you need a working slice/map/channel; use new rarely.

5. What happens if you read from a nil map vs write to one?

Reading from a nil map returns the zero value silently. Writing panics. Always initialize maps before writing.

6. How do interfaces work in Go?

Interfaces are satisfied implicitly - no implements keyword. An interface value is a two-word structure: a type pointer and a data pointer. The "nil interface" gotcha: an interface holding a nil concrete type is not equal to nil.

var p *MyError = nil
var e error = p
fmt.Println(e == nil) // false

7. What's the empty interface and when do you use it?

interface{} (or the alias any since Go 1.18) holds any value. Use sparingly - usually only at API boundaries (logging, JSON, generic containers before generics existed).

8. How do generics work in Go (post-1.18)?

func Map[T, U any](s []T, f func(T) U) []U {
    r := make([]U, len(s))
    for i, v := range s {
        r[i] = f(v)
    }
    return r
}

Generics use type parameters and constraints. The comparable and ordered constraints (Go 1.21+) cover most use cases. Avoid generics when interfaces work; use generics for collection-like utilities.

9. What's the zero value rule and why does it matter?

Every type has a defined zero value (0, "", nil, false). Variables are always initialized to zero - no uninitialized memory. This makes Go code safer than C/C++ and reduces boilerplate constructors.

10. Explain defer. What's the gotcha with loops?

defer schedules a call to run when the function returns. In LIFO order. Common gotcha: deferring inside a loop accumulates deferred calls until function exit, not loop iteration. Wrap loop bodies in a function or call Close() explicitly.


Concurrency (11-25)

11. What's a goroutine and how is it different from a thread?

A goroutine is a lightweight, user-space thread managed by the Go runtime. Initial stack is ~2KB (vs 1-8MB for OS threads). Millions of goroutines fit on a single machine. The runtime multiplexes goroutines onto OS threads (M:N scheduling).

12. Explain channels and the difference between buffered and unbuffered.

Channels are typed conduits for sending values between goroutines. Unbuffered channels synchronize: send blocks until receive. Buffered channels block only when full.

unbuf := make(chan int)        // synchronous handoff
buf := make(chan int, 10)      // up to 10 in flight

13. What does close(ch) do?

Marks the channel as closed. Receivers can drain remaining values, then receive zero values forever. Sending to a closed channel panics. Closing a closed channel panics. Only the sender should close.

14. How do you read from a closed channel safely?

v, ok := <-ch
if !ok {
    // channel closed and drained
}

15. Explain select.

select waits on multiple channel operations. Picks one ready case at random. With default, it's non-blocking.

select {
case v := <-ch1:
    handle(v)
case ch2 <- x:
    sent()
case <-time.After(1 * time.Second):
    timeout()
}

16. What's the difference between a mutex and a channel?

Channels coordinate ownership ("share memory by communicating"). Mutexes protect shared state. Use channels for handing off work, mutexes for protecting a small section of shared state. Don't use channels where a mutex is simpler.

17. What's a sync.WaitGroup and when do you use it?

Tracks completion of N goroutines. Call Add(n) before launching, Done() in each goroutine, Wait() to block.

Common bug: calling Add inside the goroutine instead of before. Always Add from the launching goroutine.

18. Explain context.Context.

Carries deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines. Always pass ctx as the first argument to functions that do I/O or take time. Never store contexts in structs.

19. How do you cancel a long-running goroutine?

Pass a context.Context and check ctx.Done() in your loop. Or use a chan struct{} close as a signal.

for {
    select {
    case <-ctx.Done():
        return ctx.Err()
    case work := <-jobs:
        process(work)
    }
}

20. What's a goroutine leak and how do you detect one?

A goroutine that's blocked forever - usually waiting on a channel that will never receive. Detect via runtime.NumGoroutine() over time, or use go.uber.org/goleak in tests.

21. Explain the Go memory model in one sentence.

Reads and writes to shared memory happen in some serial order; without explicit synchronization (channels, mutexes, atomics), goroutines may observe writes in different orders.

22. What's the difference between sync.Mutex and sync.RWMutex?

Mutex allows one holder. RWMutex allows many readers OR one writer. Use RWMutex only when reads dominate writes by 10x+ - the bookkeeping makes it slower under contention.

23. What does runtime.GOMAXPROCS control?

The maximum number of OS threads executing Go code simultaneously. Defaults to NumCPU(). Rarely needs tuning unless you're constraining a sidecar in a container.

24. How do you handle panics in goroutines?

Each goroutine must recover its own panics. Wrap goroutine bodies with defer recover() if you want to catch them.

go func() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("panic: %v", r)
        }
    }()
    work()
}()

25. What's the difference between chan T and chan<- T and <-chan T?

chan T is bidirectional. chan<- T is send-only. <-chan T is receive-only. Use directional channels in function signatures to express intent and prevent misuse.


Standard Library and Patterns (26-35)

26. How does net/http handle concurrent requests?

Each incoming request is served in its own goroutine. Handlers must be safe for concurrent use. Don't share mutable state without synchronization.

27. What's http.HandlerFunc vs http.Handler?

Handler is an interface with ServeHTTP(w, r). HandlerFunc is a type that adapts a plain function to the interface.

28. How do you implement middleware in Go?

A middleware is a function that takes a Handler and returns a Handler.

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

29. Explain io.Reader and io.Writer.

The two most-used interfaces in Go. Tiny - one method each. They compose: every file, network connection, byte buffer, gzip stream, and TLS connection implements them. Master these and the standard library opens up.

30. What's the difference between encoding/json Marshal and Encoder?

Marshal returns a []byte. Encoder writes directly to an io.Writer. Use Encoder for streaming or large outputs to avoid allocating the whole JSON buffer.

31. How do you read JSON into an unknown structure?

Decode into map[string]any or any. For partially-known schemas, use json.RawMessage to defer decoding of specific fields.

32. What's the standard way to handle errors in Go 1.20+?

Return errors as values, wrap with fmt.Errorf("doing X: %w", err), check with errors.Is and errors.As. Don't use sentinel error checks via ==. Avoid panic for control flow.

33. Explain errors.Join and when you'd use it.

Combines multiple errors into one. Useful when running parallel work where multiple operations can fail and you want to report all failures.

34. How do you write a worker pool?

jobs := make(chan Job)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        for j := range jobs {
            process(j)
        }
    }()
}
// send jobs, then close(jobs), then wg.Wait()

35. What's a fan-out/fan-in pattern?

Fan-out: one channel feeds N workers. Fan-in: N workers' outputs are merged into one channel. Standard pattern for parallelizing work.


Performance and Internals (36-43)

36. How does Go's garbage collector work in 2026?

Concurrent, tri-color, mark-sweep collector. Stop-the-world pauses are typically sub-millisecond. Tune via GOGC (default 100) - lower for less memory, higher for less CPU. GOMEMLIMIT (added in 1.19) caps total memory.

37. What is escape analysis?

The compiler decides whether a value lives on the stack (cheap) or heap (GC-tracked). Returning a pointer to a local variable causes it to escape. Use go build -gcflags="-m" to see escape decisions.

38. How do you profile a Go program?

net/http/pprof for live services. go test -cpuprofile -memprofile for benchmarks. Analyze with go tool pprof. The flame graph and top callers tell you 90% of what you need.

39. What are common allocation hot spots?

  • Repeated string concatenation in loops (use strings.Builder)
  • Boxing into interface{} triggers allocation
  • Capturing large variables in closures
  • Returning slices with capacity > length

40. Explain sync.Pool.

A free-list of reusable objects to reduce allocation pressure. Items can be evicted at any time. Use for short-lived buffers in hot paths (HTTP handlers, parsers).

41. What's the difference between buffered I/O and unbuffered I/O in Go?

os.File writes go straight to the OS - one syscall per write. Wrap in bufio.Writer to batch into larger writes. Same for bufio.Reader. The difference is often 10-100x throughput on small writes.

42. How does Go handle TLS in 2026?

crypto/tls is in the standard library. TLS 1.3 by default. Post-quantum hybrid key exchange (X25519MLKEM768) is on by default since Go 1.24. Never roll your own.

43. What's the runtime scheduler's GMP model?

G = goroutine. M = OS thread. P = processor (logical, GOMAXPROCS of them). Each P has a local run queue. M runs G's via P. Work stealing balances queues. You don't need to know the details - but mention it and senior interviewers will nod.


Systems and Design (44-50)

44. Design a rate limiter in Go.

Token bucket: a goroutine refills a buffered channel at a fixed rate; consumers receive a token before each request. Or use golang.org/x/time/rate.Limiter.

45. How would you build a graceful shutdown?

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: mux}
go srv.ListenAndServe()
<-ctx.Done()

shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(shutdownCtx)

46. Design a connection pool.

Bounded chan *Conn. Acquire = receive (with timeout). Release = send back. Health-check on acquire. The standard library's database/sql is a good reference.

47. How do you structure a large Go project?

Standard layout: cmd/<binary>/main.go, internal/ for non-exported packages, pkg/ for libraries you intend others to import. Keep packages small and focused. Avoid util packages.

48. How do you write testable Go code?

Depend on interfaces, not concrete types. Inject collaborators via struct fields or function parameters. Use table-driven tests. Use httptest.Server for HTTP handlers.

49. Walk through writing a CLI in Go.

Use flag for simple cases, spf13/cobra for nested commands. Read config from env vars (os.Getenv) or a file. Exit codes are part of the API. Always defer cleanup.

50. When would you NOT use Go?

  • High-frequency single-threaded numerical work (Rust or C++ wins)
  • Complex generic libraries pre-Go 1.18 (now better but still less expressive than Rust/Kotlin)
  • Large object-oriented domains where deep inheritance hierarchies are natural
  • Anything where you need predictable, no-GC pauses (Rust)

A senior interviewer asking this is testing whether you have the judgment to pick the right tool. Don't be a Go zealot.


Final Thoughts

The 2026 Go interview is shifting toward systems thinking. Concurrency primitives are table stakes. What separates a senior candidate is being able to reason about a service end-to-end - resource limits, backpressure, observability, graceful degradation.

Don't memorize answers. Build something nontrivial in Go (a small web service, a CLI, a job runner), break it under load, and fix it. That's what the questions are actually probing for.


Want structured Go interview prep? gitGood.dev has a Go question bank covering all of the above, plus chat-based AI mock interviews with follow-up questions and graded feedback.