A context.Context carries a cancellation signal, an optional deadline, and optional request-scoped key-value data through a call chain — including across goroutine boundaries. By strong convention, it’s passed as the first parameter of any function that does I/O or could run long, named ctx, so that cancelling one context can stop an entire tree of in-flight work (a slow database call, an HTTP request, several goroutines) with one signal.
WithCancel, WithTimeout, WithDeadline
Every context tree starts from context.Background() (or context.TODO() as a placeholder). WithCancel returns a child context plus a function that cancels it; WithTimeout/WithDeadline do the same but cancel automatically once time runs out. Cancelling a parent context cancels every context derived from it.
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() // always call cancel to release resources, even on success
result, err := doSlowThing(ctx)
if err != nil {
fmt.Println("failed or timed out:", err)
}Checking for cancellation inside a function
A function that respects context checks ctx.Done(), a channel that closes when the context is cancelled or times out, and ctx.Err() to see why. This is usually done inside a select alongside whatever real work the function is doing.
func doSlowThing(ctx context.Context) (string, error) {
select {
case <-time.After(5 * time.Second): // the actual (slow) work
return "done", nil
case <-ctx.Done(): // caller cancelled or timed out first
return "", ctx.Err() // context.DeadlineExceeded or context.Canceled
}
}WithValue: request-scoped data (used sparingly)
context.WithValue attaches a key-value pair to a context, most often used for request-scoped metadata like a trace ID or authenticated user, threaded through middleware layers. The Go team explicitly recommends against using it for passing optional function parameters — it bypasses the type system and should carry only data that’s truly request-scoped, not everyday arguments.