Go Interview Questions and Answers

Goroutines, channels, interfaces, tooling and idiomatic Go.

Practise 10 random 5 peer-reviewed questions
Go Interview Syllabus & Preparation Strategy

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 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.

2 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.

3 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".

4 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.

5 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 defer statement runs, not when the deferred call runs. defer fmt.Println(i) captures the current i.
  • Deferred functions can read and modify named return values, which enables error wrapping patterns.
  • A defer inside 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.

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.