Top 10 Golang Interview Questions

If you’re preparing for a Go interview, I’ve put together my top 10 list of the most commonly asked questions. I collected these questions myself while going through many Golang interviews. This guide walks you through ten questions you’ll almost certainly encounter, starting from the fundamentals and moving up to concurrency.

For each answer, I also provide references that I highly recommend reading to gain a deeper understanding.


1. What is a slice and how is it different from an array?

Slices are Go’s “dynamic array” and show up everywhere.
A slice is a descriptor —pointer, len, cap — over an underlying array1. Slicing shares that array; append may reallocate to a new one.

package main

import "fmt"

func main() {
    s := make([]int, 0, 2)
    s = append(s, 1, 2)      // len=2 cap=2
    p := &s[0]

    s = append(s, 3)         // reallocates
    fmt.Println("len/cap:", len(s), cap(s))
    fmt.Println("same backing?", p == &s[0]) // false after growth
}

2. Are maps ordered in Go?

Interviewers love this because it trips up people coming from other languages.
So the answer is No. Iteration order is not specified2 and can change from run to run. Don’t rely on it.

package main

import "fmt"

func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    for k := range m {
        fmt.Print(k, " ") // order is not guaranteed
    }
}

3. Can a struct be a map key?

This question tests whether you understand comparability, a cornerstone of Go’s type system.
Yes — if all fields are comparable34 (no slices, maps, or funcs).

package main

import "fmt"

type Key struct{ X, Y int } // comparable

func main() {
    m := map[Key]string{{1, 2}: "ok"}
    fmt.Println(m[Key{1, 2}]) // "ok"
}

4. What is an interface and how can a “nil interface” surprise you?

This is the most common Go pitfall when returning error. You can think of an interface as a tuple of values (type, value)5.
It’s only nil if both are nil6. A typed nil (e.g., (*T)(nil)) stored in an interface makes the interface non-nil.

package main

import "fmt"

type myErr struct{ msg string }
func (e *myErr) Error() string { return e.msg }

func mightFail() error {
    return (*myErr)(nil) // typed-nil inside an interface
}

func main() {
    err := mightFail()
    fmt.Println(err == nil) // false
    fmt.Printf("%T %v\n", err, err)
}

5. What is a goroutine and how is it different from a thread?

Concurrency is Go’s superpower.
A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space. Goroutines are lightweight7; the runtime multiplexes many goroutines onto a small pool of OS threads (M:N). GOMAXPROCS caps parallelism8.

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(50 * time.Millisecond)
            fmt.Println("goroutine", id)
        }(i)
    }
    wg.Wait()
}

6. What is a channel? (Buffered vs Unbuffered)

Channels are Go’s idiomatic way to coordinate work.
Channels are typed pipes9.

  • Unbuffered: sender and receiver synchronize at handoff.
  • Buffered: sender blocks only when the buffer is full.
package main

import "fmt"

func main() {
    unbuf := make(chan int)   // unbuffered
    go func(){ unbuf <- 42 }()
    fmt.Println(<-unbuf)

    buf := make(chan string, 2)
    buf <- "a"; buf <- "b"    // doesn't block until full
    fmt.Println(<-buf, <-buf)
}

7. Nil channels, closed channels, and who should close a channel?

These edge cases differentiate beginners from pros.

  • Nil channel: send/receive block forever10; close(nil) panics11.
  • Closed channel: receive returns zero value with ok=false10; send panics12.
  • Who closes? The sender/owner closes to signal “no more values.”

I recommend you to read fantastic post about channels axioms by Dave Cheney.

package main

import "fmt"

func main() {
    var nilch chan int
    select {
    case <-nilch: // blocks forever
    default:
        fmt.Println("nil channel would block")
    }

    c := make(chan int)
    close(c)
    v, ok := <-c
    fmt.Println(v, ok) // 0 false
}

8. Which sync primitives does Go provide and when do you use them?

Not every problem is a channel problem.

  • sync.Mutex / sync.RWMutex (protect state).
  • sync.WaitGroup (wait for goroutines).
  • sync.Once (one-time init).
  • sync/atomic (counters/flags with memory ordering)1314.

Channels are great for handoff; locks are great for protection.

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var mu sync.Mutex
    var n int64
    var wg sync.WaitGroup
    var once sync.Once

    init := func(){ fmt.Println("init once") }

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            once.Do(init)
            mu.Lock(); n++; mu.Unlock()
            atomic.AddInt64(&n, 1)
        }()
    }
    wg.Wait()
    fmt.Println("n =", n)
}

9. map vs sync.Map — when to use which?

Concurrency + performance trade-offs.

For most cases, use a normal map + your own locking or confinement.
sync.Map is specialized for read-mostly workloads15 with infrequent updates (e.g., caches).

package main

import (
    "fmt"
    "sync"
)

func main() {
    var sm sync.Map
    sm.Store("k", 1)
    if v, ok := sm.Load("k"); ok {
        fmt.Println(v)
    }
}

10. What is context and why is it important?

This is how you build cancelable, production-grade services.

context.Context carries deadlines, timeouts, cancellation signals, and request-scoped values1617.
Pass it as the first argument, derive per request, never store it, and always call the cancel func you get from WithCancel/WithTimeout/WithDeadline.

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
    defer cancel()

    done := make(chan struct{})
    go func() {
        defer close(done)
        select {
        case <-time.After(1 * time.Second):
            fmt.Println("work finished")
        case <-ctx.Done():
            fmt.Println("canceled:", ctx.Err())
        }
    }()
    <-done
}

Bonus

  • Race detector: Run go test -race or go run -race to catch data races early18.
  • Go memory model & GC: If they go deep, know that Go uses a concurrent mark-and-sweep GC with short pauses, and the memory model defines “happens-before”1920.

Conclusion

If you can explain these ten topics clearly, with a calm one-liner and a tiny example, you’ll look confident and practical. Read this post before an interview, run a few snippets, and you’ll be in great shape.


References