How to Pass a Go Interview: Top Advanced-Level Questions
A Go developer interview is not just a syntax check. At an advanced level, interviewers want to see a deep understanding of concurrency, memory management, scheduler operation, and language idioms. In this article, we will break down key questions often asked of senior engineers, with code examples and explanations. This material will be useful both for preparation and for systematizing knowledge.
1. Concurrency: Goroutines, Channels, and Synchronization Primitives
Go is famous for its built-in concurrency support. Interviewers almost always check how a candidate understands the difference between goroutines and OS threads, as well as the ability to correctly use channels and the sync package.
Question: What is the difference between a goroutine and an OS thread?
A goroutine is a lightweight thread managed by the Go scheduler (G-M-P model). It has its own stack, which starts at 2-4 KB and can grow dynamically. An OS thread is a heavyweight object with a fixed stack (usually 1-8 MB). Thousands of goroutines can run on a single OS thread, making their creation and switching very cheap.
Question: How to avoid data races when working with goroutines?
Use channels to pass data between goroutines ("Do not communicate by sharing memory; instead, share memory by communicating") or mutexes (sync.Mutex, sync.RWMutex). Always check your code with the -race flag.
var counter intvar mu sync.Mutex
func increment() { mu.Lock() counter++ mu.Unlock()}Question: Explain how select works. What happens if all channels are blocked?
select allows a goroutine to wait on multiple channel operations simultaneously. If all channels are blocked, select will wait until one of them becomes ready. If there is a default case, it will execute immediately, preventing blocking.
select {case msg := <-ch1: fmt.Println("Received from ch1:", msg)case ch2 <- "data": fmt.Println("Sent to ch2")default: fmt.Println("No channel is ready")}2. Memory Management and Garbage Collector (GC)
For an advanced level, it is important to understand how allocation works in Go, where variables are placed (stack/heap), and how the garbage collector affects performance.
Question: How does Go decide whether to place a variable on the stack or the heap?
Go uses escape analysis. If the compiler can prove that a variable is not used after the function returns (does not "escape"), it is placed on the stack. Otherwise, it is placed on the heap. For example, returning a pointer to a local variable causes it to "escape" to the heap.
func createUser() *User { u := User{Name: "Alice"} return &u // escape to heap}Question: How does the garbage collector work in Go? What phases does it go through?
Go's GC is a concurrent, tri-color mark-sweep collector. It goes through three phases:
- Mark Setup (STW): A short stop-the-world pause to enable the write barrier.
- Marking: A concurrent phase that marks live objects. It runs alongside the application.
- Sweep: Cleaning up unused memory, usually deferred.
STW (Stop The World) time is typically < 500 µs, making Go suitable for latency-sensitive applications.
Question: How to reduce GC pressure?
- Avoid frequent allocations (use object pools like
sync.Pool). - Reuse slices with
slice[:0]. - Increase the value of