context.Context carries cancellation signals, deadlines, and request-scoped values across API boundaries. Its most important job is letting work stop when the caller no longer needs it.
Start with cancellation
Create a derived context and always call its cleanup function:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
select {
case <-ctx.Done():
fmt.Println("stopped:", ctx.Err())
case <-time.After(time.Second):
fmt.Println("finished")
}
}()
cancel()
Closing ctx.Done() broadcasts cancellation to every listener. ctx.Err() explains whether cancellation or a deadline caused the stop.
Add a deadline or timeout
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
if err := fetch(ctx); err != nil {
log.Println(err)
}
Pass the context down; do not replace it with context.Background() inside fetch. Each layer may shorten a deadline, but should not silently discard its caller’s cancellation.
Make blocking work context-aware
func fetch(ctx context.Context) error {
select {
case <-time.After(time.Second):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
For HTTP clients, use http.NewRequestWithContext. Database APIs such as QueryContext follow the same pattern.
Use values sparingly
Context values are for request-scoped metadata such as a trace or request ID, not configuration or optional function parameters. Use a private key type to prevent collisions.
type requestIDKey struct{}
ctx = context.WithValue(ctx, requestIDKey{}, "req-123")
Accept a context as the first parameter, never store it in a struct unless an API specifically requires it, and never pass nil. These rules keep cancellation ownership visible.
Common mistakes
- Forgetting
cancel, which retains timers and resources longer than necessary. - Starting goroutines that never select on
ctx.Done(). - using context values as a general-purpose dependency container.
- Logging every
context.Canceledas a server failure when the client simply disconnected.
Context is most useful when every layer cooperates. A cancellation signal cannot stop code that never checks it.