Go Context
The Point
Go’s context is the standard solution for propagating cancellation signals, timeouts, and request-scoped data across goroutines. It lets an entire call chain stop cleanly at a unified point, leaving no goroutine leaks.
Explanation
The problem
An HTTP handler often runs multiple goroutines underneath – hitting the DB, calling external APIs, doing computation. Three scenarios cause trouble:
- Timeout: the request times out, and downstream work should stop too
- Cancellation: the user closes the browser mid-request; continuing is wasted work
- Request-scoped data: auth tokens and trace IDs need to flow down the call chain without adding a parameter to every function
context unifies all three into a single standard interface.
Basic usage
1ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
2defer cancel() // must call this, otherwise it leaks resources
3
4result, err := db.QueryContext(ctx, "SELECT ...")When the timeout expires, ctx.Done() closes, and all context-aware functions stop automatically.
How to receive it in a function
Functions that accept a context take ctx context.Context as the first parameter – this is a mandatory Go convention.
1func doWork(ctx context.Context) error {
2 select {
3 case <-ctx.Done():
4 return ctx.Err() // context.DeadlineExceeded or context.Canceled
5 case result := <-longOperation():
6 return process(result)
7 }
8}Four ways to create a context
| Constructor | Purpose |
|---|---|
context.Background() | Root context, used at program entry points |
context.TODO() | Placeholder when you are not sure what to use yet |
context.WithCancel(parent) | Manual cancellation |
context.WithTimeout / WithDeadline | Auto-cancel when time expires |
context.WithValue(parent, key, val) | Attach request-scoped values (use sparingly) |
Every new context is a child of its parent – cancellation only propagates from parent to child. When the parent is cancelled, all children are cancelled too.
Knowledge Sugar
Context must not be stored in a struct
Always pass it as a parameter, never put it in a struct field.
A context represents the lifetime of a request, not object state. Storing it in a struct makes the lifetime unclear.
WithValue is only for cross-cutting concerns
WithValue is for things like tracing IDs and auth tokens – data every request needs but that would pollute function signatures. Do not use it to pass regular business parameters – for those, just add a proper parameter.
Mental model
Context is a control signal for a tree. When the root says “stop,” every goroutine in the tree stops – cleanly, no leaks.