Go Interview Questions and Answers
Goroutines, channels, interfaces, tooling and idiomatic Go.
Whether you are preparing for entry-level Go interview questions for freshers or senior software engineer interview questions addressing concurrency, scalability, and system architecture, this track provides peer-reviewed model answers with syntax walkthroughs, edge cases, and practical interview tips.
1 How do slices differ from arrays in Go? Easy
An array has a fixed length that is part of its type: [4]int and [5]int are different types, and copying an array copies all elements.
A slice is a small descriptor with three fields: a pointer to an underlying array, a length and a capacity. Slices are what you use almost everywhere because they can grow.
arr := [3]int{1, 2, 3} // array, fixed
sl := []int{1, 2, 3} // slice
sl = append(sl, 4) // grows, may reallocate
sub := sl[1:3] // shares the same backing array
Because sub shares memory, writing to sub[0] changes sl[1]. append reallocates when capacity is exceeded, so two slices can unexpectedly stop aliasing. Arrays are comparable with ==; slices are not comparable except to nil. Use arrays when the size is fixed and part of the contract, and slices for almost everything else.
2 What is the difference between new and make in Go? Easy
new(T) allocates zeroed memory for a value of type T and returns a pointer *T. It works for any type and is rarely used.
make only works for slices, maps and channels. It returns an initialised value of that type (not a pointer) and lets you set length and capacity.
p := new(int) // *int, points to 0
s := make([]int, 0, 8) // len 0, cap 8
m := make(map[string]int)
ch := make(chan int, 4)
The distinction exists because slices, maps and channels need runtime initialisation before use. A zeroed map is nil, and writing to a nil map panics. new(map[string]int) returns a pointer to a nil map, which is almost never what you want. In practice people skip new for structs and take the address of a composite literal instead, for example &User{}.
3 What are zero values in Go and why do they matter? Easy
Go has no uninitialised variables. Every type has a defined zero value: 0 for numeric types, false for bool, the empty string for string, nil for pointers, slices, maps, channels, interfaces and functions, and a struct whose fields are all zero.
var n int // 0
var s string // ""
var p *int // nil
type User struct{ Name string; Age int }
var u User // {"" 0}
Because of zero values, most types are usable immediately and code can often skip constructors entirely. Idiomatic Go leans on this: a zero sync.Mutex is unlocked and ready, and a zero bytes.Buffer is empty and ready.
There are subtleties. A nil map can be read from but writing panics, so you must call make first. A nil slice can be appended to (append allocates) and ranged over safely, so var xs []int is often preferred over []int{}. Zero values are also why APIs such as errors.New work without extra setup.
4 What is a goroutine and how does it differ from an OS thread? Medium
A goroutine is a function running concurrently, scheduled by the Go runtime rather than directly by the operating system.
- Cost: goroutines start with a small growable stack (about 2KB); OS threads typically reserve 1-8MB. You can run millions of goroutines but not millions of threads.
- Scheduling: Go uses an M:N scheduler that multiplexes G goroutines onto M OS threads through P logical processors. When a goroutine blocks on a syscall or channel, the runtime hands the thread off so others keep running. The OS does not know goroutines exist.
- Communication: goroutines are designed to communicate with channels, following the idea of sharing memory by communicating rather than locking.
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); work() }()
wg.Wait()
Preemption was cooperative before Go 1.14; modern Go preempts goroutines asynchronously so a tight loop no longer starves other goroutines.
5 When should you use a buffered channel versus an unbuffered one? Medium
Channels are typed conduits for goroutine communication. Direction matters: chan T, <-chan T (receive only) and chan<- T (send only).
- Unbuffered (
make(chan int)): a send blocks until a receiver is ready, and a receive blocks until a sender is ready. This is a rendezvous that guarantees the handoff happened, useful as a synchronisation point. - Buffered (
make(chan int, n)): sends succeed until the buffer is full, then block; receives block when empty. This decouples producer and consumer and absorbs bursts.
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
for v := range ch { fmt.Println(v) }
Only the sender should close a channel; closing signals that no more values will arrive. Sending on a closed channel panics, and receiving returns the zero value plus false. select waits on multiple channels and supports timeouts. Be careful sizing buffers too large, because you lose backpressure and hide slow consumers.
6 How do interfaces work in Go, and what is the empty interface? Medium
An interface is a set of method signatures. Any type that implements all of them satisfies the interface implicitly, with no implements keyword. Internally an interface value holds a pair of the concrete type and its value.
The empty interface, interface{} (now usually written any), declares no methods, so every type satisfies it. Before generics it was the standard way to hold heterogeneous data ([]any) and is still what encoding/json uses for arbitrary documents.
var v any = "hello"
s, ok := v.(string) // type assertion, comma-ok form
switch x := v.(type) { // type switch
case string:
fmt.Println(x)
case int:
fmt.Println(x)
}
A subtle and very common bug: an interface holding a typed nil pointer is not equal to nil, because the type component is set. Functions returning interfaces should return an explicit nil on the failure path. Small, focused interfaces are idiomatic; the saying is "accept interfaces, return structs".
7 What is the difference between value receivers and pointer receivers? Medium
A method receiver can be a value (func (u User) Name() string) or a pointer (func (u *User) SetName(n string)).
- A value receiver operates on a copy, so it cannot mutate the caller's value. Its method set is available on both values and pointers.
- A pointer receiver can mutate, avoids copying large structs, and its method set is only on pointers. Go automatically takes the address when you call a pointer method on an addressable value.
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func (c Counter) Get() int { return c.n }
The practical rule: if any method mutates state, make all methods pointer receivers for consistency. Small immutable types can use value receivers. A frequent surprise is that a value stored in a map is not addressable, so you cannot call a pointer method on it. Interfaces are satisfied by the method set, so pointer receivers mean only *T implements the interface, not T.
8 How does defer work in Go and what are the common pitfalls? Medium
defer schedules a function call to run when the surrounding function returns. Deferred calls run in LIFO order and are typically used for cleanup such as closing files and unlocking mutexes.
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close()
mu.Lock()
defer mu.Unlock()
Pitfalls to know:
- Arguments are evaluated when the
deferstatement runs, not when the deferred call runs.defer fmt.Println(i)captures the currenti. - Deferred functions can read and modify named return values, which enables error wrapping patterns.
- A
deferinside a long loop accumulates until the function returns, potentially leaking file descriptors. Wrap the body in a closure or close explicitly.
for _, p := range paths {
func() {
f, _ := os.Open(p)
defer f.Close()
}()
}
Deferred calls run even when the function panics, which makes them reliable for releasing resources.
9 What is the context package used for and how do you use it correctly? Hard
context.Context carries deadlines, cancellation signals and request-scoped values across API boundaries and goroutines. It is how cancellation and timeouts propagate through a call tree.
func handler(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
_, err := http.DefaultClient.Do(req)
return err
}
Key rules:
- Cancellation propagates down the tree: cancelling a parent cancels every derived context.
- Always call the cancel function, usually with
defer, to release timers and resources. - Pass
ctxas the first parameter and checkctx.Done()in loops and blocking selects. - Use
WithValuesparingly, only for request-scoped metadata such as trace IDs, never for optional parameters. - Never store a Context inside a struct; pass it explicitly through calls.
Common bugs are ignoring cancellation so work continues after a client disconnects, and misusing values as a hidden dependency injection channel.
10 How do you choose between mutexes and channels for concurrency? Hard
Go's advice is "do not communicate by sharing memory; share memory by communicating", but both tools are legitimate.
- Use a mutex when protecting a small piece of shared state such as a counter, cache or struct field. It is simpler, cheaper and avoids goroutine leaks.
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Get(k string) int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.m[k]
}
- Use channels to transfer ownership of data, to build pipelines, to signal completion, or to coordinate a bounded worker pool. Channels give you synchronisation plus
select-based multiplexing for free.
The trade-offs: channels are heavier and overuse leads to convoluted control flow. A mutex protects state but does not coordinate lifetimes. Never copy a mutex after first use, prefer sync.RWMutex for read-heavy data, keep critical sections small, and always run go test -race to catch data races.
Frequently Asked Questions About Go Interviews
What do hiring managers evaluate in Go technical rounds?
Technical interviewers look for foundational fluency, idiomatic syntax, clarity when communicating complex logic, and awareness of performance trade-offs (e.g. memory footprint, render performance, and network latency) in production environments.
What are the best interview tips for practicing Go questions?
Use active recall: summarize each answer in your own words before revealing the model solution. Focus on explaining why a certain approach is chosen rather than just memorizing code syntax.