Go

How should a library expose context in its API, and how do you add context support to an existing API without breaking callers?

Question 289MediumGo 1.22 to 1.25

Guidelines interviewers expect:

  • Every function that does I/O, blocks, or might be slow takes ctx context.Context as its first parameter. Pure CPU helpers usually don't need one.
  • Don't make ctx optional (no variadic options, no nil check that falls back to Background). Nil contexts are a caller bug and should fail loudly.
  • Return the context's error when you stop because of it, wrapped, so callers can use errors.Is(err, context.Canceled).
  • Don't start goroutines that outlive the call unless the API says so and provides Close/Stop.
  • Don't swallow cancellation by retrying after ctx is done. Retry loops must check ctx between attempts and while sleeping.
// Backward-compatible evolution (the database/sql style)
func (c *Client) Fetch(key string) ([]byte, error) {
	return c.FetchContext(context.Background(), key)
}

func (c *Client) FetchContext(ctx context.Context, key string) ([]byte, error) {
	const maxAttempts = 3
	var lastErr error
	for attempt := range maxAttempts {
		b, err := c.try(ctx, key)
		if err == nil {
			return b, nil
		}
		lastErr = err
		if attempt == maxAttempts-1 {
			break // don't sleep after the final attempt
		}
		backoff := time.Duration(1<<attempt) * 100 * time.Millisecond
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("fetch %q: %w", key, context.Cause(ctx))
		case <-time.After(backoff): // Go 1.23+: unreferenced timers are GC'd
		}
	}
	return nil, fmt.Errorf("fetch %q: retries exhausted: %w", key, lastErr)
}

New v2 APIs should drop the old non-context variant completely.

More on Context

All 35 Context questions