Go

How do you propagate errors from multiple goroutines? Explain errgroup.

Question 313MediumGo 1.22 to 1.25

Goroutines can't return values, so errors have to be passed back explicitly. golang.org/x/sync/errgroup is the standard tool. g.Go runs functions that return error. g.Wait blocks until all of them finish and returns the first non-nil error. WithContext cancels the derived context when the first error occurs, so the other goroutines can stop early. SetLimit bounds concurrency.

func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
	g, ctx := errgroup.WithContext(ctx)
	g.SetLimit(8)
	results := make([][]byte, len(urls))
	for i, url := range urls { // Go 1.22: per-iteration i, url
		g.Go(func() error {
			b, err := fetch(ctx, url)
			if err != nil {
				return fmt.Errorf("fetch %s: %w", url, err)
			}
			results[i] = b // distinct index, no mutex needed
			return nil
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}
	return results, nil
}

Gotchas: each worker must actually respect ctx, otherwise cancellation does nothing. The context from WithContext is cancelled when Wait returns, so don't use it afterwards. Only the first error is kept. When you need all of them, collect them and use errors.Join. Go 1.25's sync.WaitGroup.Go makes launching goroutines easier, but it doesn't propagate errors.

More on Error Handling & panics

All 37 Error Handling & panics questions