Go

Write a function that runs a blocking call with a context and returns early on cancellation. What goroutine leak must you avoid?

Question 280HardGo 1.22 to 1.25

A common pattern wraps a legacy blocking function in a goroutine and uses select to wait for either the result or ctx.Done(). The trap: if the result channel is unbuffered and the context wins, the worker goroutine blocks forever trying to send, which is a leak on every timeout.

func withContext[T any](ctx context.Context, fn func() (T, error)) (T, error) {
	type result struct {
		v   T
		err error
	}
	ch := make(chan result, 1) // buffered: the sender never blocks
	go func() {
		v, err := fn()
		ch <- result{v, err}
	}()

	select {
	case r := <-ch:
		return r.v, r.err
	case <-ctx.Done():
		var zero T
		return zero, context.Cause(ctx)
	}
}

What to say in the interview:

  • This abandons the work; it does not stop it. fn keeps running and keeps holding resources. The real fix is to make fn accept a context. If that isn't possible, bound how many such goroutines can exist.
  • If the result owns resources (a file, a connection), the abandoned result must be cleaned up, for example by a goroutine that drains ch and closes it.
  • The buffer size of 1 is the key detail interviewers look for. goleak in tests catches this class of bug.

More on Context

All 35 Context questions