As asked
Write a function that takes a slice of jobs and a maximum concurrency limit, processes each job with a provided function, and returns all results. The worker count must not exceed the limit, and the function should propagate the first error encountered while still draining all remaining goroutines.
Sample answer outline
A strong answer creates a jobs channel, launches exactly N worker goroutines that range over it, sends results and errors to output channels, and uses a sync.WaitGroup to know when all workers finish. The key design decision is how to handle errors: if the goal is to process every job and collect all errors, workers should continue consuming after an error rather than returning early. If the goal is to stop on the first error, the jobs channel must be drained or a context must be cancelled so blocked senders do not hang. The first-error case uses a sync.Once to capture the error without a race. The candidate should close the jobs channel after all items are sent and the results channel after the WaitGroup is done, and should explain the tradeoff between fail-fast and drain-all semantics.
Reference implementation (go)
func WorkerPool(jobs []Job, maxWorkers int, fn func(Job) (Result, error)) ([]Result, error) {
jobCh := make(chan Job, len(jobs))
resultCh := make(chan Result, len(jobs))
errCh := make(chan error, 1)
var wg sync.WaitGroup
for i := 0; i < maxWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobCh {
r, err := fn(j)
if err != nil {
select {
case errCh <- err:
default:
}
return
}
resultCh <- r
}
}()
}
for _, j := range jobs {
jobCh <- j
}
close(jobCh)
wg.Wait()
close(resultCh)
// collect results...
}Expect these follow-ups
- How would you add context cancellation so in-flight jobs are cancelled when the context is done?
- What happens if a worker panics and you have not recovered it?