Golang Goroutine-Safe Patterns Quiz

Golang
0 Passed
0% acceptance

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.

35 Questions
~70 minutes
1

Question 1

What is a worker pool pattern in Go?

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
    }
}
A
A pattern using a fixed number of goroutines to process jobs from a channel
B
A way to create unlimited goroutines
C
A method to share memory between goroutines
D
A technique for stopping all goroutines
2

Question 2

How do you implement a producer/consumer pattern?

go
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)
}
A
Use channels to communicate between producer and consumer goroutines
B
Use shared variables
C
Use mutexes for data sharing
D
Cannot implement producer/consumer
3

Question 3

What is pipeline design in Go concurrency?

go
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)
    }
}
A
A series of stages connected by channels where each stage performs work
B
A way to run goroutines sequentially
C
A method for sharing state
D
A technique for error handling
4

Question 4

How do you avoid shared state in concurrent programs?

go
// 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
}
A
Use channels for communication instead of shared memory
B
Use global variables
C
Share pointers between goroutines
D
Cannot avoid shared state
5

Question 5

What is a common concurrency design pitfall?

A
Goroutine leak from not closing channels or not using select with default
B
Using too many goroutines
C
Using channels
D
Using sync.WaitGroup
6

Question 6

How do you implement a fan-out pattern?

go
func fanOut(in <-chan int, outA, outB chan<- int) {
    for value := range in {
        select {
        case outA <- value:
        case outB <- value:
        }
    }
    close(outA)
    close(outB)
}
A
Distribute work from one channel to multiple channels
B
Combine multiple channels into one
C
Stop all goroutines
D
Cannot fan out
7

Question 7

What is the fan-in pattern?

go
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
}
A
Combine multiple input channels into a single output channel
B
Split one channel into multiple
C
Stop channels
D
Cannot fan in
8

Question 8

How do you handle context cancellation in pipelines?

go
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
}
A
Use context.Context with select to cancel operations
B
Use panic
C
Use os.Exit
D
Cannot cancel pipelines
9

Question 9

What is the bounded buffer pattern?

go
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
}
A
Use buffered channels to limit memory usage and provide backpressure
B
Use unbuffered channels
C
Use slices for buffering
D
Cannot bound buffers
10

Question 10

How do you implement a timeout in concurrent operations?

go
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")
    }
}
A
Use select with time.After for timeout channels
B
Use time.Sleep
C
Use panic
D
Cannot implement timeouts
11

Question 11

What is the semaphore pattern?

go
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")
}
A
Use buffered channels as counting semaphores to limit concurrency
B
Use mutexes
C
Use atomic operations
D
Cannot implement semaphores
12

Question 12

How do you handle errors in concurrent pipelines?

go
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
}
A
Use structs containing both result and error for each operation
B
Use panic
C
Use log.Fatal
D
Cannot handle errors in pipelines
13

Question 13

What is the generator pattern?

go
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)
    }
}
A
Functions that return channels for lazy evaluation
B
Functions that create goroutines
C
Functions that close channels
D
Cannot generate values
14

Question 14

How do you implement a barrier synchronization?

go
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
}
A
Use sync.WaitGroup to synchronize multiple goroutines
B
Use single channel
C
Use mutexes
D
Cannot synchronize
15

Question 15

What is the tee pattern?

go
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
}
A
Duplicate a channel's output to multiple receivers
B
Combine channels
C
Stop channels
D
Cannot duplicate channels
16

Question 16

How do you prevent goroutine leaks in worker pools?

go
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
        }
    }
}
A
Ensure workers can exit when jobs channel is closed
B
Use infinite loops
C
Never close channels
D
Cannot prevent leaks
17

Question 17

What is the bridge pattern in concurrency?

go
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
}
A
Connect channels with different buffering or cancellation semantics
B
Stop goroutines
C
Share memory
D
Cannot bridge channels
18

Question 18

How do you implement a rate limiter?

go
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
}
A
Use time.Ticker to control the rate of operations
B
Use time.Sleep
C
Use sync.Mutex
D
Cannot rate limit
19

Question 19

What is the heartbeat pattern?

go
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
}
A
Periodic signals to indicate liveness
B
Error signals
C
Stop signals
D
Cannot send heartbeats
20

Question 20

How do you implement a pub/sub pattern?

go
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()
}
A
Maintain list of subscriber channels and broadcast messages
B
Use single channel
C
Use shared variables
D
Cannot implement pub/sub
21

Question 21

What is a common pitfall with channel operations?

A
Sending to closed channel causes panic
B
Receiving from closed channel is OK
C
Closing closed channel causes panic
D
All of the above
22

Question 22

How do you implement a timeout for channel operations?

go
select {
case value := <-ch:
    fmt.Println("Received:", value)
case <-time.After(time.Second):
    fmt.Println("Timeout")
}
A
Use select with time.After
B
Use time.Sleep
C
Use context
D
Cannot timeout
23

Question 23

What is the done channel pattern?

go
func worker(done <-chan struct{}, jobs <-chan int) {
    for {
        select {
        case job, ok := <-jobs:
            if !ok {
                return
            }
            process(job)
        case <-done:
            return
        }
    }
}
A
A channel used to signal cancellation or shutdown
B
A channel for job results
C
A channel for errors
D
Cannot signal done
24

Question 24

How do you handle backpressure in pipelines?

go
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
}
A
Use buffered channels to absorb temporary load differences
B
Use unbuffered channels
C
Use panic
D
Cannot handle backpressure
25

Question 25

What is the or-done channel pattern?

go
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
}
A
Combine a done channel with any other channel for cancellation
B
Stop channels
C
Share channels
D
Cannot combine channels
26

Question 26

How do you implement a sliding window?

go
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
}
A
Maintain a buffer of recent values and emit when full
B
Use time windows
C
Use channels
D
Cannot implement sliding window
27

Question 27

What is the cascading cancellation pattern?

go
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
        }
    }()
}
A
Use context.WithCancel to create cancellation hierarchies
B
Use panic
C
Use os.Exit
D
Cannot cascade cancellation
28

Question 28

How do you implement a retry mechanism with backoff?

go
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")
}
A
Retry failed operations with exponentially increasing delays
B
Retry immediately
C
Never retry
D
Cannot implement retry
29

Question 29

What is the poison pill pattern?

go
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)
    }
}
A
Send special values through channels to signal shutdown
B
Use panic
C
Use os.Exit
D
Cannot signal shutdown
30

Question 30

How do you implement a circuit breaker?

go
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
}
A
Track failures and stop calling failing operations
B
Use mutexes
C
Use atomic operations
D
Cannot implement circuit breaker
31

Question 31

What is the most important principle for goroutine-safe patterns?

A
Prefer channels over shared memory, avoid goroutine leaks, handle errors properly, use context for cancellation, test concurrent code thoroughly
B
Use as many goroutines as possible
C
Share all state
D
Never use channels
32

Question 32

How do you detect race conditions?

go
go run -race program.go
A
Use the race detector with go run -race or go test -race
B
Use fmt.Println
C
Use log.Print
D
Cannot detect races
33

Question 33

What is a goroutine leak scenario?

go
func leak() {
    ch := make(chan int)
    go func() {
        for {
            select {
            case <-ch:
                return
            default:
                // Do work but never exit
            }
        }
    }()
}
A
Goroutines that never exit, consuming memory and resources
B
Fast goroutines
C
Slow channels
D
No leaks possible
34

Question 34

How do you implement a timeout for the entire pipeline?

go
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
}
A
Use time.NewTimer with select to cancel after timeout
B
Use time.Sleep
C
Use panic
D
Cannot timeout pipelines
35

Question 35

What is the key difference between buffered and unbuffered channels?

A
Buffered channels don't block until buffer is full, unbuffered channels always block
B
Buffered channels are faster
C
Unbuffered channels use more memory
D
No difference

QUIZZES IN Golang