← Back to blog

Go (Golang) interview questions and answers

Go Golang interview questions and answers — cover from Greenroom, the AI mock interviewer

Somebody on your team wrote go func() inside a loop, captured the loop variable, and shipped it. Six weeks later a race detector run turns the CI board red and everyone stares at the same eleven lines. If you can explain why that happened, you can pass a Go interview — because Go interviews are, almost without exception, concurrency interviews wearing a light disguise.

Go is the language of modern cloud infrastructure: Docker, Kubernetes, Terraform and most of the CNCF landscape. That shapes what gets asked. Below are the Go interview questions that actually come up, with answers rather than a topic list.

Concurrency: the heart of every Go interview

What is a goroutine, and how does it differ from an OS thread?

A goroutine is a lightweight unit of concurrent execution managed by the Go runtime rather than the operating system. It starts with a small stack — a couple of kilobytes — that grows and shrinks on demand, where an OS thread typically reserves one to eight megabytes up front. The runtime multiplexes many goroutines onto a smaller pool of OS threads through its scheduler, so spawning tens of thousands is routine. The follow-up you should expect: what happens when a goroutine makes a blocking syscall? The scheduler detaches that thread and hands the remaining goroutines to another, which is why blocking I/O does not stall the whole program.

What is the difference between a buffered and an unbuffered channel?

An unbuffered channel is a synchronisation point: the send blocks until a receiver is ready, so both goroutines meet at the same instant. A buffered channel accepts up to its capacity without a receiver present, and only blocks once full. The practical consequence is that unbuffered channels give you a happens-before guarantee for free, while buffered ones decouple producer and consumer at the cost of that guarantee.

// unbuffered: send blocks until someone receives
var done = make(chan bool)
go func() {
    work()
    done <- true
}()
<-done  // waits here

// buffered: two sends proceed without a receiver
var jobs = make(chan int, 2)
jobs <- 1
jobs <- 2

What does "don't communicate by sharing memory; share memory by communicating" mean?

It is a statement about ownership. The conventional model guards shared state with a mutex, so several goroutines touch the same memory and correctness depends on every one of them locking properly. The Go idiom passes the value over a channel instead, transferring ownership: at any moment exactly one goroutine holds it, so there is nothing to guard. It is a preference, not a prohibition — sync.Mutex exists and is the right tool for a simple counter or a cache. Saying that out loud is what distinguishes someone who has used Go from someone who has read about it.

What does the select statement do?

It waits on multiple channel operations and proceeds with whichever becomes ready first, choosing at random when several are. Its most common real use is a timeout, and interviewers like it because it is compact and reveals whether you have written production Go.

select {
case res := <-ch:
    handle(res)
case <-time.After(2 * time.Second):
    return errors.New("timed out")
}

How do you detect and avoid a race condition?

Run the tests with go test -race; the race detector instruments memory access and reports conflicting unsynchronised access at runtime. It only catches races on paths that actually execute, which is worth admitting. The classic interview race is the loop-variable capture — in Go versions before 1.22, every goroutine in a for i := range loop closed over the same i. Go 1.22 changed loop variables to be per-iteration, and knowing that the language fixed it is a genuinely current answer.

WaitGroup or channel — when do you use which?

A sync.WaitGroup when you only need to know that N goroutines finished. A channel when you need their results, or need to stream them as they arrive. Reaching for a channel purely to signal completion is a common over-engineering tell.

Go interview questions — goroutines, channels, interfaces, slices and error handling
Concurrency dominates: goroutine lifetimes, channel blocking, and where ownership of data sits.

Types, interfaces and slices

How do interfaces work in Go?

Satisfaction is implicit: a type implements an interface simply by having the right method set, with no implements keyword and no dependency from the implementation to the interface. This is why Go codebases define interfaces at the point of consumption rather than alongside the type, and the associated proverb — "accept interfaces, return structs" — is a frequent follow-up. The other one interviewers enjoy: a nil interface is not the same as an interface holding a nil pointer, because the interface value carries both a type and a value word, and it is non-nil as soon as the type word is set.

What is the difference between an array and a slice?

An array has a fixed length that is part of its type, so [3]int and [4]int are different types. A slice is a three-word header — pointer, length, capacity — describing a window onto a backing array. When you append past capacity, Go allocates a larger backing array and copies, which is why append returns a new slice and why ignoring its return value is a bug. Two slices can share a backing array, so writing through one is visible through the other; that aliasing question is asked constantly.

When do you use a pointer receiver rather than a value receiver?

A pointer receiver when the method mutates the receiver, or when the struct is large enough that copying it matters. Be consistent across a type's method set — mixing them is confusing and affects which method set satisfies an interface, since only *T holds pointer-receiver methods.

Error handling and idiomatic Go

Why does Go use explicit errors instead of exceptions?

Errors are ordinary values returned alongside results, so every call site makes a visible decision about failure and control flow stays linear. The cost is verbosity, and the honest answer acknowledges the if err != nil fatigue rather than pretending it is elegant. Since Go 1.13, errors wrap with fmt.Errorf("...: %w", err) and unwrap with errors.Is and errors.As, which is the modern answer and a good signal you have written Go recently.

What are defer, panic and recover for?

defer schedules a call to run when the surrounding function returns, executing in LIFO order — used for closing files, unlocking mutexes, and ensuring cleanup on every return path. panic unwinds the stack; recover, called inside a deferred function, stops that unwinding. The idiomatic position: panic is for genuinely unrecoverable programmer error, not for ordinary failures, and a library that panics across its public API is considered badly behaved.

The core truth: a Go interview is a concurrency interview. Someone who can reason aloud about goroutine lifetimes, channel blocking and where ownership sits demonstrates real fluency — and every one of those is a verbal explanation, not something you can demonstrate by silently recognising the right answer.

How this compares to the other ways you could prepare

The Go Tour and Effective Go are the canonical sources and remain the best way to learn the semantics; the official blog posts on concurrency patterns and error wrapping are genuinely excellent. Read them. What they will not do is ask you a follow-up.

LeetCode in Go builds syntax fluency but almost never touches concurrency, which is the part being examined. You can be very good at Go array problems and still have nothing to say about channel deadlocks.

Greenroom runs the round out loud, and Ari — the AI interviewer — asks the second question: you said a buffered channel decouples producer and consumer, so what happens when the buffer fills? The honest limit is that Ari is not a compiler and will not catch a syntax error mid-sentence. What it rehearses is the thing these rounds actually test — explaining a concurrency model under mild pressure. Pair it with our backend developer and DevOps engineer guides, and our Kubernetes and Docker guides, since Go roles usually come with both.

Frequently asked questions

What are the most common Go interview questions?

Go interviews concentrate on concurrency: goroutines versus OS threads, buffered versus unbuffered channels, the select statement, WaitGroups versus channels, and detecting race conditions with go test -race. Beyond that, expect implicit interface satisfaction, arrays versus slices and how append reallocates, value versus pointer receivers, and error handling with wrapping via errors.Is and errors.As.

What is the difference between a goroutine and a thread?

A goroutine is a lightweight unit of execution scheduled by the Go runtime, starting with a stack of a couple of kilobytes that grows on demand, whereas an OS thread typically reserves one to eight megabytes up front. The runtime multiplexes many goroutines onto a smaller pool of OS threads, so running tens of thousands is normal. When a goroutine makes a blocking syscall the scheduler moves the remaining goroutines to another thread.

What is the difference between a buffered and an unbuffered channel in Go?

An unbuffered channel synchronises sender and receiver: the send blocks until a receiver is ready, giving you a happens-before guarantee. A buffered channel accepts values up to its capacity without any receiver and blocks only when full, which decouples producer from consumer but gives up that synchronisation guarantee.

How do you detect a race condition in Go?

Run your tests with go test -race. The race detector instruments memory accesses and reports unsynchronised concurrent access at runtime, though it only catches races on code paths that actually execute. The classic interview example is loop-variable capture in a goroutine, which Go 1.22 fixed by making loop variables per-iteration.

Why does Go use explicit error returns instead of exceptions?

Errors are ordinary values returned alongside results, so each call site visibly decides how to handle failure and control flow stays linear rather than jumping to a distant handler. The tradeoff is verbosity. Since Go 1.13 errors can be wrapped with fmt.Errorf and the %w verb, then inspected with errors.Is and errors.As.

How should I prepare for a Go interview?

Concentrate on the concurrency model, since it dominates these rounds: goroutine lifetimes, channel blocking behaviour, select, and where ownership of data sits. Then cover interfaces, slice aliasing and error wrapping. Because the questions are conceptual, rehearse explaining them out loud with something that asks a follow-up, rather than only reading or solving array problems.

Go interviews center on concurrency, explained out loud. Greenroom runs spoken technical interviews that follow up on your reasoning. Free to start. New to voice practice? Here's what an AI mock interview is and how it works.
Try free →