Go

How would you implement a semaphore / bounded concurrency with channels?

Question 203MediumGo 1.22 to 1.25

A buffered channel of capacity N acts as a counting semaphore: send to acquire, receive to release.

func processAll(ctx context.Context, items []Item, limit int) error {
	sem := make(chan struct{}, limit)
	var wg sync.WaitGroup
	for _, it := range items { // Go 1.22: 'it' is per-iteration
		select {
		case sem <- struct{}{}:
		case <-ctx.Done():
			wg.Wait()
			return ctx.Err()
		}
		wg.Add(1)
		go func() {
			defer wg.Done()
			defer func() { <-sem }()
			process(it)
		}()
	}
	wg.Wait()
	return nil
}

Key points: acquire before spawning the goroutine, so that at most N goroutines exist, not just N running. struct{} takes zero bytes. In production code, golang.org/x/sync/errgroup with g.SetLimit(n) or x/sync/semaphore (weighted) are usually better choices, and interviewers like hearing that you know them. The alternative is a fixed worker pool reading from a jobs channel, which works better when setting up each worker is expensive.

More on Channels & select

All 38 Channels & select questions