Skip to content

Context

context.Context carries cancellation signals, deadlines, and small amounts of request-scoped data down a call chain — it’s the standard way to say “stop what you’re doing” across goroutine boundaries.

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
select {
case <-ctx.Done():
fmt.Println("cancelled:", ctx.Err())
}
}()
cancel() // triggers ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)

If the request takes longer than 5 seconds, the context cancels it and Do returns an error — no manual timers needed.

  • ctx is always the first parameter, by convention: func DoWork(ctx context.Context, arg string).
  • Every WithCancel/WithTimeout/WithDeadline returns a cancel function — always defer cancel() even if the context finishes on its own, to release its resources immediately.
  • Never store a Context inside a struct; pass it explicitly through the call chain instead.
type key string
ctx := context.WithValue(context.Background(), key("requestID"), "abc-123")
// later, deep in the call stack
if id, ok := ctx.Value(key("requestID")).(string); ok {
log.Println("request:", id)
}

Use this sparingly — for cross-cutting metadata like request IDs or auth claims, not for passing regular function arguments.