A common mistake is using sync.WaitGroup without a clear error path. errgroup.Group.Wait blocks until every g.Go function completes, then returns the first non-nil error; errgroup also coordinates cancellation through its context. section 9.13
Replace manual error aggregation
With a plain WaitGroup, each goroutine must publish errors through a shared slice, mutex, or channel, and the parent must aggregate them afterward. With errgroup, return the error from the goroutine and check it once: `if err := g.Wait(); err != nil { return nil, err }`. Return the collected results only after Wait succeeds. section 9.13
func handler(ctx context.Context, circles []Circle) ([]Result, error) {
results := make([]Result, len(circles))
g, ctx := errgroup.WithContext(ctx) #1
for i, circle := range circles {
i := i
circle := circle
g.Go(func() error { #2
result, err := foo(ctx, circle)
if err != nil {
return err
}
results[i] = result
return nil
})
}
if err := g.Wait(); err != nil { #3
return nil, err
}
return results, nil
}
Trace the complete handler pattern: create an errgroup with context, start work with g.Go, return errors from workers, and handle the result of g.Wait. section 9.13
