The questions that come up most often, each with a sample answer you can adapt into your own words. Read them out loud until the explanation feels natural.
What is a goroutine and how does it differ from an OS thread?
IntermediateA goroutine is a lightweight, runtime-managed concurrent function started with the go keyword. It begins with a tiny stack (a few KB) that grows as needed, so you can run hundreds of thousands of them, whereas OS threads cost megabytes each. The Go scheduler multiplexes many goroutines onto a small pool of OS threads, so goroutines are far cheaper to create and switch.
What is the difference between a buffered and an unbuffered channel?
IntermediateAn unbuffered channel is synchronous: a send blocks until a receiver is ready, so it acts as a rendezvous point that synchronises two goroutines. A buffered channel holds up to its capacity without a receiver; sends block only when the buffer is full and receives block only when it is empty. Use unbuffered for handoff/synchronisation and buffered to smooth bursty producers.
How does Go handle errors, and why no exceptions?
FundamentalsFunctions return errors as ordinary values, conventionally the last return value, and callers check with if err != nil. This makes the error path explicit and local rather than hidden in a stack of throws. Errors can be wrapped with fmt.Errorf and %w to add context while preserving the chain, then inspected with errors.Is and errors.As. panic/recover exists but is reserved for truly unrecoverable situations.
How do interfaces work in Go?
IntermediateInterfaces are satisfied implicitly: any type that has the required method set implements the interface without declaring so. This decouples packages, since a consumer can define the small interface it needs and any provider satisfies it. The empty interface (interface{} or any) accepts any value, used when you genuinely need to hold an unknown type, typically narrowed with a type assertion or switch.
What is a data race and how do you detect one?
AdvancedA data race occurs when two goroutines access the same variable concurrently and at least one writes, without synchronisation, producing undefined behaviour. You prevent it by communicating over channels or guarding the variable with a sync.Mutex. Go ships a race detector: running tests or the program with the -race flag instruments memory access and reports races at runtime, which is the standard way to find them.
What does the select statement do?
Advancedselect waits on multiple channel operations and proceeds with whichever is ready, choosing randomly if several are. It is how you coordinate several channels, implement timeouts (with time.After), and add cancellation (with a context's Done channel). A default case makes the select non-blocking. It is the channel equivalent of a switch.
What is the difference between an array and a slice?
IntermediateAn array has a fixed length that is part of its type ([3]int and [4]int are different types) and is copied by value. A slice is a lightweight view (pointer, length, capacity) over a backing array and is what you use in practice. The gotcha is that slices sharing a backing array can alias each other, and append may or may not allocate a new array depending on capacity.
How do you avoid a goroutine leak?
AdvancedA goroutine leaks when it blocks forever on a channel that never receives or sends, so it is never garbage collected. You avoid it by ensuring every goroutine has a guaranteed exit path: pass a context.Context and select on ctx.Done(), close channels when done, and use sync.WaitGroup to know when workers finish. The rule is that whoever starts a goroutine is responsible for its termination.