PapersAdda
2026 Placement Season is LIVE12,000+ students preparing now

Top 40 Go (Golang) Interview Questions 2026 — Complete Guide with Solutions

25 min read
Interview Questions
Last Updated: 30 Mar 2026
Verified by Industry Experts
3,461 students found this helpful
Advertisement Placement

Go developers are among the highest-paid backend engineers in India. Mid-level Go engineers earn ₹18-35 LPA at Series B+ startups; senior Go developers at Zerodha, Razorpay, and CRED command ₹40-70 LPA. The reason? Go powers the entire cloud-native stack — Docker, Kubernetes, Prometheus, Terraform, CockroachDB are all written in Go. If you know Go deeply, you're not just a developer — you're the person who understands the infrastructure.

Go backend roles grew 200%+ in India since 2023, driven by fintechs, crypto exchanges, and cloud companies. This guide covers 40 battle-tested questions compiled from real interviews at Google, Zerodha, Razorpay, Juspay, Swiggy, and CRED — from goroutine basics to scheduler internals, covering SDE-2 through Principal Engineer levels.

Related: Docker Interview Questions 2026 | Kubernetes Interview Questions 2026 | Microservices Interview Questions 2026


What Interviewers Test in Go

LevelTopics
JuniorSyntax, goroutines basics, channels, defer, error handling
Mid-levelContext, select, interfaces, concurrency patterns, benchmarking
SeniorMemory model, scheduler internals, generics, profiling, design patterns

BEGINNER LEVEL (Questions 1–12)

Q1. What makes Go different from other languages?

Go was designed at Google to solve systems programming at scale. Its distinguishing features:

FeatureGoJava/C#Python
CompilationFast, native binarySlow, bytecodeInterpreted
MemoryGC, no manual managementGCGC
ConcurrencyGoroutines + channels (CSP)ThreadsGIL-limited
TypesStatic, structural interfacesStatic, nominalDynamic
GenericsSince 1.18Since Java 5Duck typing
Error handlingExplicit return valuesExceptionsExceptions
Binary sizeSingle static binaryJVM requiredRuntime required

Go's philosophy: simplicity, readability, and fast compilation. No implicit behavior, no magic.


Q2. Explain goroutines vs OS threads.

A goroutine is a lightweight user-space thread managed by the Go runtime, not the OS.

OS ThreadGoroutine
Stack size~1–8 MB (fixed)~2 KB (growable)
Creation costExpensive (syscall)~1–2 µs
Context switchExpensive (kernel)Cheap (runtime)
Max countThousandsMillions
CommunicationShared memory + locksChannels (CSP)
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d running\n", id)
        }(i) // pass i as argument to avoid closure capture bug
    }

    wg.Wait()
    fmt.Println("All done")
}

Common bug: capturing loop variable by reference in a goroutine without passing it as an argument.


Q3. What are channels and how do buffered channels differ from unbuffered?

Channels are typed conduits for communication between goroutines.

// Unbuffered channel — sender blocks until receiver is ready (synchronization point)
ch := make(chan int)

// Buffered channel — sender blocks only when buffer is full
bch := make(chan int, 5)

// Example: fan-out worker pool
func workerPool(jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        results <- job * job
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)
    var wg sync.WaitGroup

    // Start 3 workers
    for w := 0; w < 3; w++ {
        wg.Add(1)
        go workerPool(jobs, results, &wg)
    }

    // Send 9 jobs
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)

    // Close results after all workers finish
    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Println(result)
    }
}

Q4. What is the select statement?

select lets a goroutine wait on multiple channel operations simultaneously. It picks a case at random when multiple are ready.

func processWithTimeout(work <-chan string, timeout time.Duration) {
    timer := time.NewTimer(timeout)
    defer timer.Stop()

    for {
        select {
        case task, ok := <-work:
            if !ok {
                fmt.Println("Channel closed, done")
                return
            }
            fmt.Println("Processing:", task)

        case <-timer.C:
            fmt.Println("Timeout!")
            return

        default:
            // Non-blocking: runs if no other case is ready
            fmt.Println("No work available, idling...")
            time.Sleep(100 * time.Millisecond)
        }
    }
}

Q5. Explain defer, panic, and recover.

// defer: executes after surrounding function returns, in LIFO order
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // guaranteed to run even if panic occurs

    // process file...
    return nil
}

// defer with named return — can modify return value
func divide(a, b float64) (result float64, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered panic: %v", r)
        }
    }()

    if b == 0 {
        panic("division by zero")
    }
    return a / b, nil
}

// recover must be called directly in a deferred function
func safeGo(fn func()) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Recovered panic: %v\n%s", r, debug.Stack())
        }
    }()
    fn()
}

Pitfall: defer in a loop creates deferred calls that don't execute until the function returns, not each loop iteration. Move the body to a separate function.


Q6. How does error handling work in Go?

Go uses explicit error return values. Errors are values that implement the error interface.

// Basic error handling
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Wrapping errors (Go 1.13+)
func processOrder(id string) error {
    order, err := db.GetOrder(id)
    if err != nil {
        return fmt.Errorf("processOrder %s: %w", id, err)
    }
    // ...
    return nil
}

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

// Unwrapping errors
err := processOrder("order-123")
var dbErr *DatabaseError
if errors.As(err, &dbErr) {
    log.Printf("DB error code: %d", dbErr.Code)
}
if errors.Is(err, ErrNotFound) {
    return http.StatusNotFound
}

Q7. What are interfaces in Go?

Go interfaces are implicit — a type implements an interface by having all the required methods (structural typing, not declared).

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64      { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64      { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }

// Polymorphic function
func printShape(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

// Empty interface (prefer 'any' in Go 1.18+)
func printAny(v any) {
    switch t := v.(type) {
    case int:    fmt.Printf("int: %d\n", t)
    case string: fmt.Printf("string: %s\n", t)
    default:     fmt.Printf("unknown: %T\n", t)
    }
}

Q8. What is the context package and why is it important?

context.Context carries deadlines, cancellation signals, and request-scoped values across goroutines and API boundaries.

func fetchUserData(ctx context.Context, userID string) (*User, error) {
    // Respect context deadline/cancellation
    req, err := http.NewRequestWithContext(ctx, "GET", "/user/"+userID, nil)
    if err != nil {
        return nil, err
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        if ctx.Err() != nil {
            return nil, fmt.Errorf("request cancelled: %w", ctx.Err())
        }
        return nil, err
    }
    defer resp.Body.Close()
    // parse response...
}

// HTTP handler with timeout
func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
    defer cancel() // ALWAYS defer cancel to prevent context leak

    user, err := fetchUserData(ctx, r.URL.Query().Get("id"))
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}

// Context values — use typed keys to avoid collisions
type contextKey string

const requestIDKey contextKey = "requestID"

func withRequestID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, requestIDKey, id)
}

func getRequestID(ctx context.Context) string {
    if id, ok := ctx.Value(requestIDKey).(string); ok {
        return id
    }
    return ""
}

Q9. What is a goroutine leak and how do you prevent it?

A goroutine leak occurs when a goroutine is started but never exits — it blocks forever on a channel receive, waiting for something that never comes.

// LEAK: goroutine blocks forever if nobody sends on ch
func badSearch(query string) chan string {
    ch := make(chan string)
    go func() {
        result := expensiveSearch(query)
        ch <- result // blocks forever if caller abandons the channel
    }()
    return ch
}

// FIX: use context for cancellation
func goodSearch(ctx context.Context, query string) <-chan string {
    ch := make(chan string, 1) // buffered so goroutine doesn't block
    go func() {
        result := expensiveSearch(query)
        select {
        case ch <- result:
        case <-ctx.Done(): // caller cancelled — goroutine exits cleanly
        }
    }()
    return ch
}

Tools: goleak (uber-go) for detecting leaks in tests.


Q10. Explain Go's memory model briefly.

Go's memory model defines when reads of variables in one goroutine are guaranteed to observe writes from another. Key guarantees:

  • Within a goroutine: operations execute in the order they appear.
  • Between goroutines: a send on a channel happens-before the corresponding receive completes.
  • sync.Mutex: Unlock happens-before any subsequent Lock.
  • Once.Do: the call to Do(f) completes before any call returns.

Without synchronization, the Go compiler and CPU may reorder operations. Always use channels, mutexes, or sync/atomic to synchronize concurrent access.


Q11. What is the difference between new and make?

// new(T) allocates zero-value of T and returns *T
p := new(int)    // *int pointing to 0
s := new([]int)  // *[]int pointing to nil slice header

// make(T, ...) initializes and returns T (for slice, map, chan only)
sl := make([]int, 5, 10)  // slice with len=5, cap=10
m  := make(map[string]int) // initialized map (nil map panics on write)
ch := make(chan int, 2)    // buffered channel

// In practice, composite literals are preferred
sl2 := []int{0, 0, 0, 0, 0}
m2  := map[string]int{}

Q12. How does Go handle nil?

Go's nil is a zero value for pointers, channels, maps, slices, interfaces, and functions. Key subtleties:

// Interface nil trap — the most common gotcha
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }

// BAD: returns non-nil interface with nil pointer
func getError(fail bool) error {
    var err *MyError
    if fail {
        err = &MyError{"something failed"}
    }
    return err // even when err == nil pointer, this is NOT nil interface!
}

// GOOD:
func getErrorFixed(fail bool) error {
    if fail {
        return &MyError{"something failed"}
    }
    return nil // returns nil interface
}

// Nil slice is usable (not nil map which panics on write)
var s []int
s = append(s, 1, 2, 3) // works fine
len(s) // 3

var m map[string]int
m["key"] = 1 // PANIC: assignment to nil map

Confident on Q1-Q12? You've cleared the Go screening round at most companies. The intermediate section covers generics, context, concurrency patterns, and profiling — the exact topics that separate ₹15 LPA offers from ₹30 LPA+ offers.

INTERMEDIATE LEVEL — Essential Go Patterns (Questions 13–28)

Q13. Explain Go generics (Go 1.18+) with practical examples.

Go 1.18 introduced generics with type parameters. Go 1.21 added slices, maps, and cmp built-in packages built on generics.

// Generic function with type constraint
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, fn func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

// Type constraint with interface
type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// Generic data structures
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) }

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    last := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return last, true
}

// Usage
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
total := Sum(nums) // 15

Q14. What are common concurrency patterns in Go?

Pattern 1: Pipeline

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

// Usage: generate → square → consume
for n := range square(generate(2, 3, 4)) {
    fmt.Println(n) // 4, 9, 16
}

Pattern 2: Fan-Out, Fan-In

func merge(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    output := func(ch <-chan int) {
        defer wg.Done()
        for v := range ch {
            merged <- v
        }
    }

    wg.Add(len(channels))
    for _, ch := range channels {
        go output(ch)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()

    return merged
}

Pattern 3: Done channel for cancellation

func doWork(done <-chan struct{}, stream <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for v := range stream {
            select {
            case out <- v * 2:
            case <-done:
                return
            }
        }
    }()
    return out
}

Q15. How does sync.Mutex differ from sync.RWMutex?

// Mutex: exclusive lock — one goroutine at a time
type SafeCounter struct {
    mu sync.Mutex
    v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.v[key]++
}

// RWMutex: multiple concurrent readers OR one writer
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()         // Multiple concurrent reads allowed
    defer c.mu.RUnlock()
    v, ok := c.data[key]
    return v, ok
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // Exclusive write lock
    defer c.mu.Unlock()
    c.data[key] = value
}

Use RWMutex when reads heavily outnumber writes (e.g., in-memory caches).


Q16. What is sync.Once and what is it used for?

// Thread-safe singleton initialization
type Database struct {
    conn *sql.DB
}

var (
    instance *Database
    once     sync.Once
)

func GetDB() *Database {
    once.Do(func() {
        conn, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
        if err != nil {
            panic(err)
        }
        instance = &Database{conn: conn}
    })
    return instance
}

// sync.Once for lazy initialization
type Config struct {
    once sync.Once
    data map[string]string
}

func (c *Config) Load() map[string]string {
    c.once.Do(func() {
        c.data = loadFromFile("config.yaml")
    })
    return c.data
}

Q17. Explain Go's scheduler (M:N threading model).

Go uses an M:N threading model: M goroutines are multiplexed across N OS threads (where N = GOMAXPROCS, default = number of CPUs).

Components:

  • G (Goroutine): the lightweight unit of work
  • M (Machine): OS thread
  • P (Processor): scheduling context, holds a run queue of goroutines

When a goroutine blocks on a syscall, the P detaches from M and attaches to another M, keeping the CPU busy. This is why millions of goroutines can run with only a handful of OS threads.

Work stealing: idle Ps steal goroutines from other Ps' run queues for load balancing.


Q18. How do you write benchmarks and profile Go code?

// Benchmark with testing.B
func BenchmarkSum(b *testing.B) {
    nums := make([]int, 1000)
    for i := range nums {
        nums[i] = i
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Sum(nums)
    }
}

// Table-driven benchmarks
func BenchmarkMap(b *testing.B) {
    sizes := []int{100, 1000, 10000}
    for _, size := range sizes {
        b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
            nums := make([]int, size)
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                Map(nums, func(n int) int { return n * 2 })
            }
        })
    }
}

Run benchmarks: go test -bench=. -benchmem -cpuprofile cpu.prof

Profile: go tool pprof cpu.prof


Q19. What is the errgroup package?

golang.org/x/sync/errgroup runs goroutines concurrently and collects their errors.

import "golang.org/x/sync/errgroup"

func fetchAll(ctx context.Context, userID string) (*UserData, error) {
    g, ctx := errgroup.WithContext(ctx)

    var user *User
    var orders []Order
    var preferences *Preferences

    g.Go(func() error {
        var err error
        user, err = fetchUser(ctx, userID)
        return err
    })

    g.Go(func() error {
        var err error
        orders, err = fetchOrders(ctx, userID)
        return err
    })

    g.Go(func() error {
        var err error
        preferences, err = fetchPreferences(ctx, userID)
        return err
    })

    if err := g.Wait(); err != nil {
        return nil, err // returns first non-nil error
    }

    return &UserData{User: user, Orders: orders, Prefs: preferences}, nil
}

Q20. Real-World Scenario: Rate limiter using channels

type RateLimiter struct {
    tokens chan struct{}
    ticker *time.Ticker
    quit   chan struct{}
}

func NewRateLimiter(rps int) *RateLimiter {
    rl := &RateLimiter{
        tokens: make(chan struct{}, rps),
        ticker: time.NewTicker(time.Second / time.Duration(rps)),
        quit:   make(chan struct{}),
    }

    // Fill initial tokens
    for i := 0; i < rps; i++ {
        rl.tokens <- struct{}{}
    }

    // Refill tokens at the rate
    go func() {
        for {
            select {
            case <-rl.ticker.C:
                select {
                case rl.tokens <- struct{}{}: // non-blocking add
                default: // bucket full, discard
                }
            case <-rl.quit:
                return
            }
        }
    }()

    return rl
}

func (rl *RateLimiter) Allow() bool {
    select {
    case <-rl.tokens:
        return true
    default:
        return false
    }
}

func (rl *RateLimiter) Stop() {
    rl.ticker.Stop()
    close(rl.quit)
}

Q21–Q28: Quick Intermediate Round

Q21. What is the difference between a value receiver and pointer receiver? Value receiver: gets a copy, cannot modify the original. Pointer receiver: gets a pointer, can modify original. Use pointer receivers when the method needs to mutate state, the struct is large, or the type has mutex/sync fields. All methods on a type should consistently use one or the other.

Q22. What is go vet and golangci-lint? go vet is the built-in static analyzer that catches common mistakes (wrong format verbs, unreachable code, suspicious mutex usage). golangci-lint aggregates 50+ linters including staticcheck, errcheck, gosec.

Q23. How does garbage collection work in Go? Go uses a concurrent tri-color mark-and-sweep GC. From Go 1.17+, it aims for sub-millisecond STW (stop-the-world) pauses. Tune with GOGC (percentage of heap growth before GC triggers, default 100) and GOMEMLIMIT (soft memory ceiling, Go 1.19+).

Q24. What is an init() function? A special function that runs after package-level variable initialization, before main(). Each package can have multiple init() functions. Use sparingly — they make order of initialization hard to reason about.

Q25. How does the range keyword work? range iterates over arrays, slices, strings, maps, and channels. For slices: returns index, value copy. For maps: returns key, value copy (non-deterministic order). For strings: returns byte index, rune. For channels: receives values until channel is closed.

Q26. What is the stringer interface? Any type implementing String() string satisfies fmt.Stringer. The fmt package calls this when printing the value. Generate with go generate + stringer tool for enum-like types.

Q27. What is unsafe.Pointer and when should you use it? unsafe.Pointer bypasses Go's type system, converting between pointer types. Used for performance-critical serialization, interfacing with C via cgo, or implementing low-level data structures. Almost never needed in application code.

Q28. How do you write table-driven tests?

func TestDivide(t *testing.T) {
    tests := []struct {
        name    string
        a, b    float64
        want    float64
        wantErr bool
    }{
        {"normal division", 10, 2, 5, false},
        {"divide by zero", 10, 0, 0, true},
        {"negative", -10, 2, -5, false},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Divide(tt.a, tt.b)
            if (err != nil) != tt.wantErr {
                t.Errorf("Divide() error = %v, wantErr %v", err, tt.wantErr)
            }
            if !tt.wantErr && got != tt.want {
                t.Errorf("Divide() = %v, want %v", got, tt.want)
            }
        })
    }
}

If you've mastered Q1-Q28, you're already in the top 15% of Go candidates in India. The advanced section covers scheduler internals, memory model, and production system design — these questions decide ₹40 LPA+ offers at Zerodha, Google, and Cloudflare.

ADVANCED LEVEL — The No-BS Senior Round (Questions 29–40)

Q29. Implement a concurrent-safe LRU cache in Go.

import (
    "container/list"
    "sync"
)

type LRUCache[K comparable, V any] struct {
    capacity int
    mu       sync.Mutex
    items    map[K]*list.Element
    list     *list.List
}

type entry[K comparable, V any] struct {
    key   K
    value V
}

func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V] {
    return &LRUCache[K, V]{
        capacity: capacity,
        items:    make(map[K]*list.Element),
        list:     list.New(),
    }
}

func (c *LRUCache[K, V]) Get(key K) (V, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()

    if elem, ok := c.items[key]; ok {
        c.list.MoveToFront(elem)
        return elem.Value.(*entry[K, V]).value, true
    }
    var zero V
    return zero, false
}

func (c *LRUCache[K, V]) Put(key K, value V) {
    c.mu.Lock()
    defer c.mu.Unlock()

    if elem, ok := c.items[key]; ok {
        c.list.MoveToFront(elem)
        elem.Value.(*entry[K, V]).value = value
        return
    }

    if c.list.Len() == c.capacity {
        // Evict least recently used
        back := c.list.Back()
        if back != nil {
            c.list.Remove(back)
            delete(c.items, back.Value.(*entry[K, V]).key)
        }
    }

    e := &entry[K, V]{key: key, value: value}
    elem := c.list.PushFront(e)
    c.items[key] = elem
}

Q30. How does Go handle circular imports and how do you break them?

Go does not allow circular imports — it's a compile error. Break circular dependencies by:

  1. Extracting the shared type into a third package
  2. Using interfaces instead of concrete types
  3. Restructuring the package hierarchy
// CIRCULAR: packageA imports packageB, packageB imports packageA

// FIX: Extract shared types to packageC
// packageA imports packageC
// packageB imports packageC
// packageA does NOT import packageB

Q31. What is slog (Go 1.21) and how does structured logging work?

import "log/slog"

// Default logger — outputs to stderr
slog.Info("user logged in", "userId", "u123", "ip", "192.168.1.1")

// Custom JSON logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
    AddSource: true,
}))

// With context
ctx := context.Background()
logger.InfoContext(ctx, "order created",
    slog.String("orderId", "o456"),
    slog.Int("amount", 2999),
    slog.Group("user",
        slog.String("id", "u123"),
        slog.String("email", "[email protected]"),
    ),
)

// Logger with fixed attributes
orderLogger := logger.With("service", "order-service", "version", "1.2.3")

Q32. How do you implement graceful shutdown in a Go HTTP server?

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    })

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    // Start server in goroutine
    serverError := make(chan error, 1)
    go func() {
        slog.Info("Starting server", "addr", server.Addr)
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            serverError <- err
        }
    }()

    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    select {
    case err := <-serverError:
        log.Fatalf("Server error: %v", err)
    case sig := <-quit:
        slog.Info("Shutting down", "signal", sig)
    }

    // Graceful shutdown with deadline
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Forced shutdown: %v", err)
    }

    slog.Info("Server stopped gracefully")
}

Q33–Q40: Advanced Quick Fire

Q33. What is cgo and what are its tradeoffs? cgo allows Go programs to call C code. Tradeoffs: cross-compilation becomes harder, build times increase, CGo function calls are 10–100x slower than Go calls (due to goroutine-to-OS-thread handoff), and static binary guarantee is broken.

Q34. How does Go 1.21's slices package improve performance? slices.Sort uses pdqsort (pattern-defeating quicksort), faster than the generic sort before. Functions like slices.Contains, slices.Index reduce boilerplate. All are generic and work with any comparable/ordered type.

Q35. What is the iter package (Go 1.23)? Introduces range-over-function iterators. Functions with signature func(yield func(V) bool) can now be used directly in for range loops, enabling lazy sequences without channels overhead.

Q36. Explain Go's escape analysis. The compiler determines at compile time whether a variable's lifetime can be confined to the stack or must be heap-allocated. Variables that escape (returned, stored in heap-allocated struct, passed to interface) go to the heap and incur GC pressure. Check with go build -gcflags='-m' ./....

Q37. What is sync.Pool and when to use it? sync.Pool caches temporary objects to reduce GC pressure in hot paths. Objects may be evicted at any GC cycle. Typical use: bytes.Buffer, HTTP request/response objects, serialization buffers. Not for persistent state.

Q38. How do you implement the options pattern for constructors?

type Server struct { port int; timeout time.Duration; maxConn int }
type Option func(*Server)
func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func NewServer(opts ...Option) *Server {
    s := &Server{port: 8080, timeout: 30*time.Second, maxConn: 100}
    for _, opt := range opts { opt(s) }
    return s
}

Q39. What are type assertions vs type switches? Type assertion: v, ok := i.(ConcreteType) — extracts the concrete value from an interface. Type switch: switch v := i.(type) { case T1: ... } — branches on the dynamic type of an interface. Type switches are idiomatic for handling multiple possible types.

Q40. Real-World: How would you build a task queue with workers in Go? Channel-based: buffered channel as the queue, goroutine pool as workers, sync.WaitGroup to drain, context for cancellation. For persistence, use Redis (with go-redis) or a DB-backed queue. For distributed systems, use message queues (NATS, Kafka) with consumer groups.


Common Go Mistakes — Avoid These and Impress Every Interviewer

  1. Goroutine leak — goroutine blocked on channel with no sender/receiver, no cancellation
  2. Nil map writevar m map[string]int; m["key"] = 1 panics; always make(map[...]...)
  3. Defer in loop — defers accumulate until function returns, not loop iteration
  4. Closing a closed channel — panics; only the sender should close
  5. Not using context.WithCancel and always calling cancel — context leak
  6. Interface nil trap — returning a typed nil as error gives non-nil interface
  7. Loop variable capturego func() { use(i) }() captures reference, not value
  8. Using time.Sleep for synchronization — use channels or sync primitives

FAQ — Your Go Career Questions, Answered

Q: Is Go good for web development? Yes. The standard library's net/http is production-grade. Frameworks like Gin, Echo, Chi, and Fiber add routing and middleware. For full-stack, Templ (Go HTML templates) + HTMX is gaining traction.

Q: Go vs Rust — which should I learn for backend roles in India? Go has far more job openings in India (Zerodha, Juspay, Razorpay, Swiggy, CRED use Go). Rust is excellent for systems programming but fewer application backend roles. Go first, Rust if you want systems/infra roles.

Q: How important are goroutine leaks in interviews? Very important at senior level. Be able to identify leaks in code snippets and describe the fix. goleak in tests is a strong answer.

Q: What Go projects should I build for a portfolio? CLI tools, REST APIs with PostgreSQL, gRPC services, a simple key-value store, or a load balancer. Demonstrating real concurrency (not just a hello-world goroutine) matters.

Q: Is Go's error handling verbose? Yes, and it's intentional. Companies like it because errors are visible and explicit. In interviews, show you understand idiomatic error wrapping with %w and errors.As/errors.Is.

Q: What is the recommended project layout for Go? cmd/ for main packages, internal/ for private packages, pkg/ for public libraries (optional), api/ for protobuf/OpenAPI specs. See github.com/golang-standards/project-layout.

Q: How do you pass Go interview coding rounds? Practice LeetCode in Go — especially graph problems (BFS/DFS using goroutines is a bonus point), string manipulation, and sliding window. Know the difference between recursive and iterative approaches. Use sort.Slice and the new slices package fluently.

Q: What is Go's approach to dependency management? Go modules (go.mod/go.sum) since Go 1.11. Use go get, go mod tidy, go mod vendor. The Go proxy (proxy.golang.org) caches modules for reproducibility.


Practice: Implement a concurrent URL crawler, rate limiter, and in-memory key-value store with TTL. These cover goroutines, channels, mutexes, and context — the core of Go interviews.

Go is the language of infrastructure. Master it and you'll never run out of high-paying opportunities. Bookmark this, build a concurrent project, and walk into your next interview with confidence.

Related Articles:

Advertisement Placement

Explore this topic cluster

More resources in Interview Questions

Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.

Company hub

Explore all Top 40 Go (Golang) resources

Open the Top 40 Go (Golang) hub to jump between placement papers, interview questions, salary guides, and other related pages in one place.

Open Top 40 Go (Golang) hub

Related Articles

More from PapersAdda

Share this guide: