Master Go interviews with 50+ questions on goroutines, channels, interfaces, and concurrent programming.
10 Questions
~30 min read
Goroutines are lightweight threads managed by Go runtime, not OS. They start with ~2KB stack (grows dynamically) vs ~1MB for OS threads. Thousands of goroutines can run on few OS threads (M:N scheduling). Created with "go" keyword. Cheaper to create/destroy than threads. Go runtime handles scheduling, making concurrent code simpler to write.
Channels enable goroutine communication and synchronization. Types: unbuffered (synchronous, blocks until received), buffered (async up to capacity). Operations: send (ch <- v), receive (v := <-ch), close(ch). Select statement handles multiple channels. Patterns: fan-in, fan-out, worker pools. Close channels when no more values will be sent; receivers can detect closed channels.
Data race: concurrent read/write to same variable without synchronization. Prevention: (1) Channels - communicate by sharing, (2) sync.Mutex/RWMutex for locking, (3) sync/atomic for atomic operations, (4) sync.Once for one-time initialization. Detect with: go run -race. Design principle: share memory by communicating, don't communicate by sharing memory.
new(T) allocates zeroed memory, returns pointer (*T). make(T, args) creates slices, maps, channels - returns initialized (not zeroed) value of type T. Use new for structs; make for reference types that need initialization. Example: new(int) returns *int pointing to 0; make([]int, 5) returns initialized slice with len=cap=5.
Interfaces define method signatures; any type implementing those methods satisfies the interface implicitly (no "implements" keyword). Empty interface (interface{} or any) accepts any type. Interface values hold (type, value) pairs. Use for: polymorphism, dependency injection, testing (mock implementations). Keep interfaces small; accept interfaces, return concrete types.
Go uses explicit error returns instead of exceptions. Errors are values implementing error interface. Best practices: (1) Check errors immediately, (2) Add context with fmt.Errorf("context: %w", err), (3) Use errors.Is/As for comparison, (4) Create custom error types for specific handling, (5) Don't ignore errors, (6) Handle errors once. Go 1.13+ supports error wrapping.
Arrays: fixed size, value type (copied on assignment), size is part of type ([5]int != [10]int). Slices: dynamic, reference type (share underlying array), have length and capacity. Slices are more common. Create slice: make([]int, len, cap) or []int{1,2,3}. Append may allocate new array if capacity exceeded. Be careful with shared backing arrays.
Go uses concurrent, tri-color mark-and-sweep GC. Optimized for low latency (<1ms pauses in Go 1.8+). GOGC environment variable controls frequency (default 100 = GC when heap doubles). Memory: stack (goroutine-local, cheap), heap (shared, GC'd). Escape analysis determines stack vs heap allocation. Use sync.Pool for frequently allocated objects.
Context carries deadlines, cancellation signals, and request-scoped values across API boundaries. Types: Background (root), TODO (placeholder), WithCancel, WithDeadline, WithTimeout, WithValue. Pass as first parameter to functions. Use for: cancellation propagation, timeouts, passing request IDs. Don't store context in structs; pass explicitly.
Standard layout: /cmd (main applications), /internal (private code), /pkg (public libraries), /api (API definitions), /configs, /scripts, /test. Go modules (go.mod) for dependency management. Keep main packages small; business logic in internal. Follow: simple, flat structures for small projects; organized packages for large ones. No src directory needed.