How do you collect all errors from concurrent workers rather than just the first?
Question 314MediumGo 1.22 to 1.25
Give each worker its own slot in a pre-sized slice. Each goroutine writes a different element, so there is no data race and no mutex. Then call errors.Join, which skips nil entries and returns nil if every slot is nil.
func RunAll(jobs []func() error) error {
errs := make([]error, len(jobs))
var wg sync.WaitGroup
for i, job := range jobs {
wg.Add(1)
go func() {
defer wg.Done()
if err := job(); err != nil {
errs[i] = fmt.Errorf("job %d: %w", i, err)
}
}()
}
wg.Wait() // happens-before: all writes are visible after Wait
return errors.Join(errs...)
}
Alternatives: a buffered channel make(chan error, len(jobs)) that you drain after Wait, or a mutex-guarded append. With an unbuffered channel, workers block unless someone is reading at the same time, a classic goroutine leak and deadlock. Close result channels only after wg.Wait(), usually from a separate goroutine, when the reader ranges over the channel.
The interviewer is checking how you reason about memory: why is writing to distinct slice indices safe while append is not?
More on Error Handling & panics
- Q312Compare
panic,os.Exit,log.Fatalandruntime.Goexitwith respect to deferred functions. - Q313How do you propagate errors from multiple goroutines? Explain
errgroup. - Q315Go errors don't carry stack traces. How do you get stack information when you need it?
- Q316"Handle an error only once." What does that mean, and what is wrong with log-and-return?
- Q317Why is
defer f.Close()potentially a bug when writing files? How do you handle theCloseerror? - Q318What does this print? (Shadowed
err)