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.Contextas 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
ctxis done. Retry loops must checkctxbetween 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
- Q287How do you test context-aware code, including timeouts and cancellation?
- Q288Is a context safe for concurrent use? What happens if you read ctx.Done() from many goroutines, or call cancel concurrently?
- Q290What does this print? (AfterFunc on already-canceled and live contexts)
- Q291What does this print? (Cause across WithoutCancel)
- Q292How do you make a range-over-func iterator (iter.Seq) respect context cancellation, and why is it better than a channel-based generator?
- Q293How do semaphore.Weighted and rate.Limiter use context, and what subtle bugs appear with them?