Golang Goroutine-Safe Patterns Quiz
35 comprehensive questions on Golang's goroutine-safe patterns, covering worker pools, producer/consumer patterns, pipeline design, avoiding shared state, and concurrency design pitfalls — with 15 code examples demonstrating safe concurrent programming practices.
Question 1
What is a worker pool pattern in Go?
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
<-results
}
}Question 2
How do you implement a producer/consumer pattern?
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func consumer(ch <-chan int) {
for value := range ch {
fmt.Println("Consumed:", value)
}
}
func main() {
ch := make(chan int, 5)
go producer(ch)
consumer(ch)
}Question 3
What is pipeline design in Go concurrency?
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
c := generator(1, 2, 3, 4)
out := square(c)
for result := range out {
fmt.Println(result)
}
}Question 4
How do you avoid shared state in concurrent programs?
// Bad: shared state
// var counter int
// var mu sync.Mutex
// Good: no shared state
type SafeCounter struct {
mu sync.Mutex
value int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}Question 5
What is a common concurrency design pitfall?
Question 6
How do you implement a fan-out pattern?
func fanOut(in <-chan int, outA, outB chan<- int) {
for value := range in {
select {
case outA <- value:
case outB <- value:
}
}
close(outA)
close(outB)
}Question 7
What is the fan-in pattern?
func fanIn(input1, input2 <-chan int) <-chan int {
c := make(chan int)
go func() {
for {
select {
case s := <-input1:
c <- s
case s := <-input2:
c <- s
}
}
}()
return c
}Question 8
How do you handle context cancellation in pipelines?
func pipelineStage(ctx context.Context, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for {
select {
case value, ok := <-in:
if !ok {
return
}
// Process value
out <- value * 2
case <-ctx.Done():
return
}
}
}()
return out
}Question 9
What is the bounded buffer pattern?
func boundedBuffer(in <-chan int, size int) <-chan int {
out := make(chan int, size)
go func() {
for value := range in {
out <- value
}
close(out)
}()
return out
}Question 10
How do you implement a timeout in concurrent operations?
func withTimeout(operation func() error, timeout time.Duration) error {
done := make(chan error, 1)
go func() {
done <- operation()
}()
select {
case err := <-done:
return err
case <-time.After(timeout):
return errors.New("operation timed out")
}
}Question 11
What is the semaphore pattern?
var sem = make(chan struct{}, 3)
func worker(id int) {
sem <- struct{}{} // Acquire
defer func() { <-sem }() // Release
fmt.Println("Worker", id, "working")
time.Sleep(time.Second)
fmt.Println("Worker", id, "done")
}Question 12
How do you handle errors in concurrent pipelines?
type Result struct {
Value int
Err error
}
func processWithError(in <-chan int) <-chan Result {
out := make(chan Result)
go func() {
defer close(out)
for value := range in {
if value < 0 {
out <- Result{Err: errors.New("negative value")}
continue
}
out <- Result{Value: value * 2}
}
}()
return out
}Question 13
What is the generator pattern?
func count(to int) <-chan int {
ch := make(chan int)
go func() {
for i := 1; i <= to; i++ {
ch <- i
}
close(ch)
}()
return ch
}
func main() {
for num := range count(5) {
fmt.Println(num)
}
}Question 14
How do you implement a barrier synchronization?
func barrier(workers int) (chan<- struct{}, <-chan struct{}) {
start := make(chan struct{})
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go func(id int) {
defer wg.Done()
<-start // Wait for start signal
fmt.Println("Worker", id, "working")
}(i)
}
go func() {
close(start) // Signal all workers to start
wg.Wait() // Wait for all workers to finish
close(done)
}()
return start, done
}Question 15
What is the tee pattern?
func tee(in <-chan int) (<-chan int, <-chan int) {
out1 := make(chan int)
out2 := make(chan int)
go func() {
defer close(out1)
defer close(out2)
for value := range in {
out1 <- value
out2 <- value
}
}()
return out1, out2
}Question 16
How do you prevent goroutine leaks in worker pools?
func worker(id int, jobs <-chan Job, results chan<- Result) {
for job := range jobs {
select {
case results <- process(job):
case <-time.After(time.Minute):
// Timeout
return
}
}
}Question 17
What is the bridge pattern in concurrency?
func bridge(done <-chan struct{}, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for {
select {
case value, ok := <-in:
if !ok {
return
}
select {
case out <- value:
case <-done:
return
}
case <-done:
return
}
}
}()
return out
}Question 18
How do you implement a rate limiter?
func rateLimit(rate int) chan time.Time {
ch := make(chan time.Time)
go func() {
ticker := time.NewTicker(time.Second / time.Duration(rate))
defer ticker.Stop()
for t := range ticker.C {
ch <- t
}
}()
return ch
}Question 19
What is the heartbeat pattern?
func heartbeat(interval time.Duration) <-chan time.Time {
ch := make(chan time.Time)
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case t := <-ticker.C:
select {
case ch <- t:
default:
// Drop if no receiver
}
}
}
}()
return ch
}Question 20
How do you implement a pub/sub pattern?
type Publisher struct {
subscribers []chan int
mu sync.RWMutex
}
func (p *Publisher) Subscribe() <-chan int {
ch := make(chan int, 1)
p.mu.Lock()
p.subscribers = append(p.subscribers, ch)
p.mu.Unlock()
return ch
}
func (p *Publisher) Publish(value int) {
p.mu.RLock()
for _, ch := range p.subscribers {
select {
case ch <- value:
default:
// Drop if full
}
}
p.mu.RUnlock()
}Question 21
What is a common pitfall with channel operations?
Question 22
How do you implement a timeout for channel operations?
select {
case value := <-ch:
fmt.Println("Received:", value)
case <-time.After(time.Second):
fmt.Println("Timeout")
}Question 23
What is the done channel pattern?
func worker(done <-chan struct{}, jobs <-chan int) {
for {
select {
case job, ok := <-jobs:
if !ok {
return
}
process(job)
case <-done:
return
}
}
}Question 24
How do you handle backpressure in pipelines?
func stage(in <-chan int) <-chan int {
out := make(chan int, 100) // Buffered channel
go func() {
defer close(out)
for value := range in {
// Slow processing
time.Sleep(time.Millisecond * 10)
out <- value * 2
}
}()
return out
}Question 25
What is the or-done channel pattern?
func orDone(done, c <-chan int) <-chan int {
valStream := make(chan int)
go func() {
defer close(valStream)
for {
select {
case <-done:
return
case v, ok := <-c:
if !ok {
return
}
select {
case valStream <- v:
case <-done:
return
}
}
}
}()
return valStream
}Question 26
How do you implement a sliding window?
func slidingWindow(in <-chan int, windowSize int) <-chan []int {
out := make(chan []int)
go func() {
defer close(out)
window := make([]int, 0, windowSize)
for value := range in {
window = append(window, value)
if len(window) > windowSize {
window = window[1:]
}
if len(window) == windowSize {
result := make([]int, windowSize)
copy(result, window)
out <- result
}
}
}()
return out
}Question 27
What is the cascading cancellation pattern?
func withCancel(parent context.Context) (context.Context, context.CancelFunc) {
return context.WithCancel(parent)
}
func operation(ctx context.Context) {
childCtx, cancel := withCancel(ctx)
defer cancel()
go func() {
select {
case <-childCtx.Done():
// Cancelled
return
}
}()
}Question 28
How do you implement a retry mechanism with backoff?
func retryWithBackoff(operation func() error, maxRetries int) error {
backoff := time.Millisecond * 100
for i := 0; i < maxRetries; i++ {
if err := operation(); err == nil {
return nil
}
time.Sleep(backoff)
backoff *= 2
}
return errors.New("max retries exceeded")
}Question 29
What is the poison pill pattern?
type Job struct {
Data string
Poison bool
}
func worker(jobs <-chan Job) {
for job := range jobs {
if job.Poison {
// Shutdown signal
return
}
process(job.Data)
}
}Question 30
How do you implement a circuit breaker?
type CircuitBreaker struct {
failures int
threshold int
mu sync.Mutex
}
func (cb *CircuitBreaker) Call(operation func() error) error {
cb.mu.Lock()
if cb.failures >= cb.threshold {
cb.mu.Unlock()
return errors.New("circuit open")
}
cb.mu.Unlock()
err := operation()
cb.mu.Lock()
if err != nil {
cb.failures++
} else {
cb.failures = 0
}
cb.mu.Unlock()
return err
}Question 31
What is the most important principle for goroutine-safe patterns?
Question 32
How do you detect race conditions?
go run -race program.goQuestion 33
What is a goroutine leak scenario?
func leak() {
ch := make(chan int)
go func() {
for {
select {
case <-ch:
return
default:
// Do work but never exit
}
}
}()
}Question 34
How do you implement a timeout for the entire pipeline?
func pipelineWithTimeout(in <-chan int, timeout time.Duration) <-chan int {
out := make(chan int)
go func() {
defer close(out)
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case value, ok := <-in:
if !ok {
return
}
select {
case out <- value:
case <-timer.C:
return
}
case <-timer.C:
return
}
}
}()
return out
}Question 35
What is the key difference between buffered and unbuffered channels?
