Go errgroup primer
The errgroup package from golang.org/x/sync is the canonical way to coordinate goroutines that can fail. It batches concurrent work, propagates the first error, and cancels in-flight siblings through a shared context.
1. Why errgroup beats sync.WaitGroup
A raw sync.WaitGroup waits but cannot surface errors and has no built-in cancellation. With errgroup you get both: the first non-nil error returned by any goroutine short-circuits the group, cancels the derived context, and bubbles up through Wait().
2. Fan-out with shared context
Use errgroup.WithContext(parent) to derive a context that is cancelled when any goroutine returns an error. Pass that context into every blocking call so HTTP requests, DB queries, and timers terminate cleanly instead of leaking.
3. Reference implementation
Parallel HTTP fetch with bounded fan-out and first-error cancellation:
package main
import (
"context"
"fmt"
"net/http"
"golang.org/x/sync/errgroup"
)
func fetchAll(ctx context.Context, urls []string) error {
g, ctx := errgroup.WithContext(ctx)
results := make([]int, len(urls))
for i, url := range urls {
i, url := i, url
g.Go(func() error {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
results[i] = resp.StatusCode
return nil
})
}
if err := g.Wait(); err != nil {
return fmt.Errorf("fetch failed: %w", err)
}
return nil
}