Prev Next

Golang / GoLang Concurrency Mastery Interview Questions

1. Explain Go's GMP scheduler model. What are M, P, and G and how do they interact? 2. What is work stealing in Go's scheduler and why does it matter for performance? 3. Why can Go run millions of goroutines while equivalent OS-thread workloads fail? 4. What is the difference between unbuffered and buffered channels in Go? 5. What happens when you send to, receive from, or close a nil or closed channel? 6. How does the select statement work in Go and what are its key properties? 7. What is a data race in Go, how do you detect it, and what are the three main fixes? 8. When do you use sync.Mutex versus sync.RWMutex, and what are the critical usage rules? 9. How does sync.WaitGroup work and what are the most common mistakes? 10. What is sync.Once and what guarantees does it provide? 11. What causes deadlocks in Go and how do you detect and prevent them? 12. What are directional channels in Go and why use them in function signatures? 13. What causes goroutine leaks and how do you prevent and detect them? 14. Implement fan-out and fan-in concurrency patterns in Go. 15. How do you use a nil channel in select to dynamically enable or disable cases? 16. When should you use the sync/atomic package instead of sync.Mutex? 17. Implement a bounded worker pool pattern in Go. 18. What are the differences between time.After, time.NewTimer, and time.NewTicker, and which leaks resources? 19. How does context.Context enable clean goroutine cancellation and why is 'defer cancel()' critical? 20. How do you use a buffered channel as a semaphore to limit goroutine concurrency? 21. What is the 'done channel' pattern and why has context.Context largely superseded it? 22. What is the goroutine loop-variable capture bug and how do you fix it? 23. How does golang.org/x/sync/errgroup simplify concurrent error handling? 24. When should a channel carry 'chan struct{}' versus a typed value, and why is close() used for broadcast? 25. What is GOMAXPROCS, how does it affect parallelism, and what is the container pitfall? 26. How does Go's select handle multiple ready cases, and how do you implement true priority? 27. What is sync.Cond and when do you use it instead of channels? 28. Implement Go's canonical pipeline pattern with cancellation from the Go blog. 29. What is Go's memory model and why does it matter for concurrent code? 30. What is the check-then-act race condition (TOCTOU) and how do you fix it? 31. What is asynchronous preemption in Go (1.14+) and why was it introduced? 32. How do you safely use a map from multiple goroutines in Go? 33. How do you use a buffered channel as a task queue with natural backpressure? 34. How does Go handle goroutines that make blocking syscalls — what happens to M and P? 35. Implement a simple publish-subscribe broker using Go channels. 36. What is the lock-held-during-I/O anti-pattern and how do you fix it? 37. Write a complete example of implementing operation timeouts in Go using select. 38. How do you write tests that detect goroutine leaks automatically? 39. Implement a concurrent word count across multiple files — a classic Go interview puzzle. 40. How does GOMAXPROCS=1 change behaviour and when is it actually useful? 41. How do you implement a high-performance sharded concurrent map in Go? 42. What are the specific happens-before guarantees for channel operations in Go's memory model? 43. How do you implement a hedged request pattern using select and goroutines? 44. How does sync.Pool reduce GC pressure in high-throughput Go services? 45. What is a livelock and how does it differ from a deadlock in Go programs? 46. How do you implement backpressure in Go to prevent overloading downstream systems? 47. Implement a lock-free stack using atomic CAS operations and explain the ABA problem. 48. Summarise: channel vs mutex decision guide, and the top concurrency pitfalls.

1. Explain Go's GMP scheduler model. What are M, P, and G and how do they interact?

Go uses an M:N scheduler — M goroutines multiplexed onto N OS threads, managed by the Go runtime. The three key entities are: GMP Entities Entity Symbol Description Goroutine G Lightweight user-space thread with its own 2 KB stack. Contains the goroutine's code, stack pointer, and scheduling stat...

Read full answer

2. What is work stealing in Go's scheduler and why does it matter for performance?

Work stealing is the mechanism that keeps all Ps (logical CPUs) busy even when goroutine load is unevenly distributed. It is the key reason Go programs efficiently use all available CPU cores without manual thread pool management. How it works: each P maintains a local run queue — a lock-free rin...

Read full answer

3. Why can Go run millions of goroutines while equivalent OS-thread workloads fail?

Three fundamental differences make goroutines dramatically cheaper than OS threads: initial stack size, scheduling cost, and blocking behaviour. Goroutine vs OS Thread Aspect OS Thread Goroutine Initial stack 1–8 MB (fixed, kernel-allocated) 2 KB (grows dynamically up to 1 GB) Scheduling Preempti...

Read full answer

4. What is the difference between unbuffered and buffered channels in Go?

This is the most fundamental channel question in every Go interview. The two types have completely different synchronisation semantics. Unbuffered vs Buffered Channel Aspect Unbuffered (make(chan T)) Buffered (make(chan T, N)) Capacity 0 N Send blocks when No goroutine is ready to receive Buffer ...

Read full answer

5. What happens when you send to, receive from, or close a nil or closed channel?

This table is essential interview knowledge. Mistakes here produce panics and deadlocks that are notoriously difficult to debug. Channel Operation Reference Table Operation Nil channel Open, empty channel Open, has data Closed channel Send (ch <- v) Blocks forever Blocks Sends OK PANIC Receive (<...

Read full answer

6. How does the select statement work in Go and what are its key properties?

select is Go's multiplexed channel operation. It waits on multiple channel operations simultaneously and proceeds with the first one that is ready. It is the primary tool for non-blocking operations, timeouts, and cancellation in concurrent code. // Basic select — whichever channel has data fir...

Read full answer

7. What is a data race in Go, how do you detect it, and what are the three main fixes?

A data race occurs when two or more goroutines access the same memory location concurrently, at least one access is a write, and there is no synchronisation between them. Data races produce undefined behaviour: silent data corruption, non-deterministic results, or rare crashes. // Classic data ra...

Read full answer

8. When do you use sync.Mutex versus sync.RWMutex, and what are the critical usage rules?

sync.Mutex provides mutual exclusion — at most one goroutine holds the lock at any time. sync.RWMutex distinguishes readers from writers, allowing multiple concurrent readers OR one exclusive writer — more efficient for read-heavy workloads. // sync.Mutex — mixed or write-heavy access type Bank...

Read full answer

9. How does sync.WaitGroup work and what are the most common mistakes?

sync.WaitGroup is a counter-based synchronisation primitive. One goroutine calls Wait() to block until all tracked goroutines have called Done() . The counter starts at zero, increases with Add(n) , and decreases with Done() (equivalent to Add(-1) ). // CORRECT usage pattern var wg sync.WaitGroup...

Read full answer

10. What is sync.Once and what guarantees does it provide?

sync.Once guarantees that a function is executed exactly once , regardless of how many goroutines concurrently call Do() . It is the idiomatic Go approach for thread-safe lazy initialisation and singletons. // Thread-safe singleton with sync.Once var ( instance * Database once sync.Once ) func Ge...

Read full answer

11. What causes deadlocks in Go and how do you detect and prevent them?

A deadlock occurs when every goroutine is blocked waiting for a resource held by another — a circular dependency from which no goroutine can proceed. The Go runtime detects simple all-goroutine deadlocks and panics with 'all goroutines are asleep — deadlock!' . // DEADLOCK 1: circular channel wai...

Read full answer

12. What are directional channels in Go and why use them in function signatures?

Go channels can be typed with a direction: chan<- T (send-only) or <-chan T (receive-only). A bidirectional chan T can be assigned to either. Directional channels enforce access discipline at compile time, making the data-flow contract of each function explicit. // Producer: only sends to out —...

Read full answer

13. What causes goroutine leaks and how do you prevent and detect them?

A goroutine leak occurs when a goroutine starts but never terminates — it blocks indefinitely waiting on a channel, lock, or I/O that will never complete. Leaked goroutines accumulate over time, consuming memory and potentially holding references that prevent GC, causing steady memory growth unti...

Read full answer

14. Implement fan-out and fan-in concurrency patterns in Go.

Fan-out: distribute work from one source to multiple worker goroutines. Fan-in: merge results from multiple goroutines back into a single channel. Together they form Go's fundamental parallel pipeline pattern. // Fan-out: distribute jobs to N workers func fanOut(ctx context.Context, jobs <- chan ...

Read full answer

15. How do you use a nil channel in select to dynamically enable or disable cases?

A nil channel case in select is permanently disabled — it never fires. This allows you to dynamically enable or disable select cases at runtime by toggling a channel variable between nil and a real channel, without any if-else branching inside the select. // Scenario: drain primary channel; only ...

Read full answer

16. When should you use the sync/atomic package instead of sync.Mutex?

The sync/atomic package provides lock-free operations on individual primitive values using CPU-level instructions (LOCK prefix on x86, load-linked/store-conditional on ARM). It is faster than a mutex for simple single-variable operations but is limited to supported types and single-variable updat...

Read full answer

17. Implement a bounded worker pool pattern in Go.

A worker pool limits the number of goroutines working concurrently, preventing resource exhaustion (file handles, DB connections, memory) when processing a large number of tasks. It is one of the most commonly asked Go patterns in technical interviews. type Job struct { ID int ; Payload string } ...

Read full answer

18. What are the differences between time.After, time.NewTimer, and time.NewTicker, and which leaks resources?

All three involve time-based channel operations but have distinct behaviours, reuse capabilities, and resource management responsibilities. Time Facility Comparison API Returns Fires Resource leak risk Reusable time.After(d) <-chan Time Once, after d Goroutine + channel until d expires if case no...

Read full answer

19. How does context.Context enable clean goroutine cancellation and why is 'defer cancel()' critical?

context.Context is Go's standard mechanism for propagating cancellation, deadlines, and request-scoped values across API boundaries. Every blocking or long-running function should accept a context as its first parameter. // Creating contexts ctx, cancel := context.WithCancel(context.Background())...

Read full answer

20. How do you use a buffered channel as a semaphore to limit goroutine concurrency?

A buffered channel of capacity N acts as a counting semaphore — at most N goroutines can be in a critical section simultaneously. This is a simple, idiomatic Go pattern for throttling concurrent HTTP requests, database queries, or any resource-bounded operation. // Semaphore: limit to 10 concurre...

Read full answer

21. What is the 'done channel' pattern and why has context.Context largely superseded it?

Before context.Context was standardised in Go 1.7, a chan struct{} was the primary way to broadcast a stop signal. Closing the channel (rather than sending) is used because it unblocks all waiting receivers simultaneously — a broadcast, not a point-to-point signal. // Done channel pattern (pre-1....

Read full answer

22. What is the goroutine loop-variable capture bug and how do you fix it?

One of the most common Go concurrency interview puzzles. When a goroutine closure captures a loop variable by reference, all goroutines in the loop share the same variable — which has already advanced to its final value by the time any goroutine runs. // BUG (Go 1.21 and earlier): all goroutines ...

Read full answer

23. How does golang.org/x/sync/errgroup simplify concurrent error handling?

errgroup.Group is a higher-level abstraction over sync.WaitGroup that adds automatic error collection and optional context cancellation. It is the idiomatic tool for the pattern of running N goroutines and returning the first non-nil error. import "golang.org/x/sync/errgroup" // Basic errgroup â€...

Read full answer

24. When should a channel carry 'chan struct{}' versus a typed value, and why is close() used for broadcast?

chan struct{} is the Go idiom for pure signalling — when the fact that something occurred matters, but no data needs to be transferred. struct{}{} has zero size (no memory allocated for the value), and using it explicitly communicates 'this is a signal only'. // chan struct{} — pure event signa...

Read full answer

25. What is GOMAXPROCS, how does it affect parallelism, and what is the container pitfall?

GOMAXPROCS controls the number of OS threads (Ps) that can execute Go code simultaneously. It defaults to runtime.NumCPU() — the number of logical CPU cores on the host. Misunderstanding its default is one of the most common production performance issues for containerised Go services. import "run...

Read full answer

26. How does Go's select handle multiple ready cases, and how do you implement true priority?

When multiple cases in a select are simultaneously ready, Go picks one uniformly at random . This prevents deterministic starvation but does not guarantee priority. Implementing true priority requires nested selects or a dedicated check before the main select. // Standard select : random among re...

Read full answer

27. What is sync.Cond and when do you use it instead of channels?

sync.Cond is a condition variable — a synchronisation primitive for goroutines to wait for or announce a state change on a shared resource. Use it when: many goroutines wait for a complex shared-state predicate; waking all waiters at once is needed; or channels would add unnecessary data-passing ...

Read full answer

28. Implement Go's canonical pipeline pattern with cancellation from the Go blog.

The Go blog defines three-stage pipelines: a generator that produces values, one or more transformation stages, and a consumer — all connected by directional channels, with cancellation via context. // Stage 1: Generator func generate(ctx context.Context, nums ... int ) <- chan int { out := make(...

Read full answer

29. What is Go's memory model and why does it matter for concurrent code?

Go's memory model defines which memory operations in one goroutine are guaranteed to be visible to operations in another. Without understanding it, concurrent code may appear to work correctly in tests but fail silently in production under different compiler optimisations or CPU architectures. Ke...

Read full answer

30. What is the check-then-act race condition (TOCTOU) and how do you fix it?

The Time Of Check, Time Of Use (TOCTOU) race: a goroutine checks a condition, releases the lock, then acts — but between check and act another goroutine changes the condition. The fix is to hold the lock for the entire check-and-act sequence, or use an atomic compare-and-swap. // BUGGY: check-the...

Read full answer

31. What is asynchronous preemption in Go (1.14+) and why was it introduced?

Before Go 1.14, goroutine scheduling was cooperative : a goroutine only yielded its processor at specific safe points — function call sites, channel operations, and syscalls. A CPU-bound goroutine in a tight loop with no function calls could starve other goroutines indefinitely and block GC stop-...

Read full answer

32. How do you safely use a map from multiple goroutines in Go?

Go maps are not safe for concurrent use . The runtime detects concurrent map access and throws a fatal error: 'concurrent map read and map write' . There are three main solutions, each with different performance trade-offs. // BUGGY: concurrent map access — runtime fatal error var m = map[strin...

Read full answer

33. How do you use a buffered channel as a task queue with natural backpressure?

A buffered channel provides natural backpressure: the producer blocks when the queue is full, signalling the consumer cannot keep up. This prevents unbounded memory growth without any additional data structure and is idiomatic Go. type TaskQueue struct { tasks chan func () quit chan struct {} } f...

Read full answer

34. How does Go handle goroutines that make blocking syscalls — what happens to M and P?

When a goroutine makes a blocking OS syscall (file I/O, sleep), Go must not stall its P — other goroutines need to continue running. The runtime handles this through handoff : the P detaches from the blocked M and continues execution on a new M. // What happens when goroutine G1 calls os . ReadFi...

Read full answer

35. Implement a simple publish-subscribe broker using Go channels.

A pub-sub system decouples publishers from subscribers. The idiomatic Go implementation uses a broker goroutine that owns the subscriber registry and distributes messages — a single goroutine owning a map eliminates all locking. type Broker[T any ] struct { publish chan T subscribe chan chan <- T...

Read full answer

36. What is the lock-held-during-I/O anti-pattern and how do you fix it?

Holding a mutex while performing I/O (network calls, disk reads) serialises all goroutines that need that lock for the entire I/O duration. This collapses concurrency to near-zero throughput — one of the most common Go performance mistakes. // ANTI-PATTERN: lock held during HTTP call type Cache s...

Read full answer

37. Write a complete example of implementing operation timeouts in Go using select.

Timeouts are critical for preventing goroutine leaks in service calls. The idiomatic Go approach uses context.WithTimeout (production preferred) or time.After (quick one-off). A crucial detail: the result channel must be buffered to avoid a goroutine leak when the timeout fires first. // Pattern ...

Read full answer

38. How do you write tests that detect goroutine leaks automatically?

Goroutine leaks are among the hardest production bugs to diagnose — they accumulate invisibly. Catching them at test time is far more effective than debugging production memory growth. import ( "testing" "runtime" "time" "go.uber.org/goleak" ) // Method 1 : goleak — recommended, most accurate f...

Read full answer

39. Implement a concurrent word count across multiple files — a classic Go interview puzzle.

This exercise tests goroutine spawning, WaitGroup usage, channel fan-in, and safe result aggregation. It is a common live-coding assignment in Go technical screens. package main import ( "bufio" "context" "fmt" "os" "sync" ) type FileCount struct { File string Words int Err error } func countWord...

Read full answer

40. How does GOMAXPROCS=1 change behaviour and when is it actually useful?

With GOMAXPROCS=1 , only one goroutine executes at any instant — goroutines interleave cooperatively but never run truly in parallel. This changes the scheduling behaviour but does not eliminate race conditions. runtime.GOMAXPROCS( 1 ) // With GOMAXPROCS=1: only one goroutine runs at a time // Th...

Read full answer

41. How do you implement a high-performance sharded concurrent map in Go?

A single mutex protecting one map is a bottleneck under high concurrency. A sharded map divides the key space across N independent maps, each with its own mutex, reducing contention by approximately N-fold. Goroutines with keys in different shards can operate in parallel without competing. import...

Read full answer

42. What are the specific happens-before guarantees for channel operations in Go's memory model?

Go's memory model specifies precise happens-before rules for channels. Knowing these is necessary for writing correct concurrent code that works across CPU architectures without data races. Channel Happens-Before Rules Operation Guarantee Send on a channel Completes before the receive from that s...

Read full answer

43. How do you implement a hedged request pattern using select and goroutines?

A hedged request sends the same request to multiple backends simultaneously and returns the first successful response, cancelling the rest. It trades slightly higher resource use for dramatically lower tail latency — a technique from Google's Bigtable paper. func hedgedFetch(ctx context.Context, ...

Read full answer

44. How does sync.Pool reduce GC pressure in high-throughput Go services?

sync.Pool is a concurrent pool of reusable temporary objects. By returning objects to the pool after use instead of discarding them, allocation and GC pressure are significantly reduced — critical for high-throughput services like HTTP servers and JSON encoders. // Pool of byte buffers reused per...

Read full answer

45. What is a livelock and how does it differ from a deadlock in Go programs?

A deadlock : all goroutines are blocked — no progress is possible. A livelock : all goroutines are actively running but constantly reacting to each other in a loop that prevents any meaningful progress — like two people in a corridor who keep stepping in the same direction to let the other pass. ...

Read full answer

46. How do you implement backpressure in Go to prevent overloading downstream systems?

Backpressure is the mechanism by which a slow consumer signals a fast producer to slow down. Without it, producers overwhelm consumers, causing unbounded queue growth, OOM, or cascading failures. Go's channels provide natural backpressure — the most important property to communicate in interviews...

Read full answer

47. Implement a lock-free stack using atomic CAS operations and explain the ABA problem.

A lock-free data structure uses compare-and-swap (CAS) instead of mutexes — concurrent access without blocking. This is an advanced topic demonstrating deep understanding of memory ordering and Go's atomic package. // Lock - free stack using atomic . Pointer (Go 1.19 + ) type node[T any] struct {...

Read full answer

48. Summarise: channel vs mutex decision guide, and the top concurrency pitfalls.

This summary covers the most tested concurrency patterns and pitfalls in Go technical interviews. Channel vs Mutex — Decision Guide Scenario Recommended Tool Passing data ownership between goroutines Channel Signalling an event / broadcasting Channel (close for broadcast) Parallel pipeline of tra...

Read full answer

«
»

Comments & Discussions