errgroup already cancels the context for you

Goconcurrencybackend

I used to hand-roll a context.WithCancel alongside an errgroup.Group, calling cancel() myself when one goroutine failed. Turns out errgroup.WithContext does exactly this, and I was duplicating it.

g, ctx := errgroup.WithContext(parent)
for _, url := range urls {
    url := url
    g.Go(func() error {
        return fetch(ctx, url)
    })
}
if err := g.Wait(); err != nil {
    return err
}

The ctx it returns is cancelled the moment the first goroutine returns a non-nil error, or when Wait returns. Every other goroutine that respects the context stops on its own. No manual cancel, no leaked work chasing a result nobody wants anymore.

One caveat worth remembering: Wait returns only the first error. If you need all of them, collect into a slice guarded by a mutex, or reach for errors.Join after the fact. But for the common “fan out, fail fast” shape, WithContext is the whole pattern in one line.