// lesson: errgroup-from-scratch

errgroup From Scratch

Almost every fan-out in production code has the same requirements: launch N tasks, wait for all of them, surface the first error, and usually bound how many run at once. golang.org/x/sync/errgroup packages exactly that, and its API is tiny:

var g errgroup.Group
g.SetLimit(4)
for _, u := range urls {
	g.Go(func() error { return fetch(u) })
}
if err := g.Wait(); err != nil { /* first failure */ }

The value is in the precise semantics, so state them before building:

  • Go(f) runs f in its own goroutine. With a limit set, Go blocks until a slot frees up โ€” backpressure at the submission point, so a producer loop can never race ahead of the pool.
  • Wait blocks until every launched f has returned, then yields the first non-nil error. Not the last, not a combined bag: the first, because later failures are usually just fallout from the first one.
  • The zero value is ready to use โ€” the Go idiom of making the empty struct meaningful, like bytes.Buffer and sync.Mutex.

Every piece is something you have already built in this course: a WaitGroup for "all done", a first-writer-wins slot for the error (exactly the Once semantics from earlier โ€” sync.Once is fair game here), and a buffered-channel semaphore for the limit. The subtle part is memory visibility: Wait reads the stored error after wg.Wait(), and the failing goroutine wrote it before wg.Done(), so the WaitGroup edge โ€” not luck โ€” is what makes the read safe.

The real errgroup also has errgroup.WithContext(ctx), which returns a *Group paired with a derived context that is canceled the moment any task returns an error โ€” the plain zero-value Group above has no context of its own. That cancel-on-first-error shape is the same muscle you built in ParallelMap, so here we focus on just the group.

โ€บ Build the Group

30 pts

Implement (the real errgroup is not available in the grader โ€” stdlib only):

type Group struct { /* ... */ }

func (g *Group) SetLimit(n int)      // max n tasks in flight; call before any Go
func (g *Group) Go(f func() error)   // run f concurrently; block if at the limit
func (g *Group) Wait() error         // wait for all; return the first error

Requirements:

  • The zero value of Group is usable without a limit.
  • Tasks started with Go run concurrently, not synchronously.
  • Wait returns only after every task has finished, and returns the first error that occurred (nil if none).
  • After SetLimit(n), at most n tasks are in flight at once; Go blocks while the group is full.

Log in to submit a solution and earn points.