Go

Print "foo", "bar", "baz" in strict order from three goroutines, repeated N times.

Question 518MediumGo 1.22 to 1.25

Build a ring of channels. Goroutine i waits on chs[i], prints, and then signals chs[(i+1)%3]. Main starts it by putting a token into chs[0]. This generalizes to any number of workers.

func FooBarBaz(n int) {
	words := []string{"foo", "bar", "baz"}
	chs := make([]chan struct{}, len(words))
	for i := range chs {
		chs[i] = make(chan struct{}, 1) // buffer of 1: see gotcha
	}
	var wg sync.WaitGroup
	for i, w := range words {
		wg.Add(1)
		go func() { // Go 1.22+: i and w are per-iteration, safe to capture
			defer wg.Done()
			next := chs[(i+1)%len(chs)]
			for range n {
				<-chs[i]
				fmt.Println(w)
				next <- struct{}{}
			}
		}()
	}
	chs[0] <- struct{}{}
	wg.Wait()
}

Gotcha: on the last round "baz" sends a token into chs[0], but "foo" has already exited. With unbuffered channels that final send blocks forever and wg.Wait() deadlocks. You can fix it with a buffer of 1, as above, or by skipping the send on the last iteration. The leftover token is simply garbage collected.

Alternative: use one sync.Mutex + sync.Cond with a shared turn counter. Each goroutine waits for turn%3 != id, prints, increments, and calls Broadcast. Here you must use Broadcast, not Signal, because Signal might wake the wrong goroutine, which goes back to sleep, and then nobody makes progress.

What the interviewer is looking for: knowing that the scheduler guarantees nothing about order, so ordering has to be enforced explicitly (a time.Sleep "fix" fails immediately). Also that you handle the termination edge case and, before Go 1.22, the loop-variable capture bug.

More on Classic Concurrency Coding Problems

All 16 Classic Concurrency Coding Problems questions