Context: Cancellation & Deadlines
context.Context is the most important type in modern Go
programs that do anything networked. It threads three concerns through
your call stack: cancellation signals, deadlines/timeouts,
and request-scoped values.
Learning objectives
- Explain why Go added
context. - Create a context with cancel, timeout, or deadline.
- Respect a context inside your functions by listening on
ctx.Done(). - Attach request-scoped values, and know when not to.
- Pass context as the first argument through your call stack.
Why context exists
Imagine a web request that kicks off three database calls, two external HTTP requests, and a log write. If the user cancels the HTTP connection, every goroutine doing that work should stop; otherwise you waste CPU, memory, and database connections.
Context is how Go propagates "stop what you're doing" down a call tree.
The Context interface
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
The important one is Done(): a channel that closes when
the context is cancelled or expired. You select on it
alongside your actual work.
context.Background() and context.TODO()
ctx := context.Background() // root context, never cancelled
ctx := context.TODO() // "I don't know what to pass yet", semantically equivalent, signals intent
Every context chain starts from one of these at main (or
an HTTP handler, or a test). You derive child contexts from them.
context.WithCancel
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // always call cancel to release resources
go func() {
select {
case <-time.After(5 * time.Second):
fmt.Println("work done")
case <-ctx.Done():
fmt.Println("cancelled:", ctx.Err())
}
}()
cancel() // signal cancellation
Always defer cancel(), even if you think
the context will expire on its own. It's cheap and prevents leaks.
WithTimeout and WithDeadline
// Fire cancellation 2 seconds from now
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// Or: fire at a specific time
ctx, cancel := context.WithDeadline(parent, time.Now().Add(5*time.Minute))
defer cancel()
The child context is cancelled when (a) the timeout/deadline fires,
or (b) the parent is cancelled, or (c) you call cancel()
explicitly. Whichever happens first.
WithValue
type userIDKey struct{} // unexported key type avoids collisions
ctx := context.WithValue(ctx, userIDKey{}, 42)
if v := ctx.Value(userIDKey{}); v != nil {
fmt.Println("user:", v.(int))
}
ctx.Value is for request-scoped values like
trace IDs, auth tokens, and logger handles, values that cross API
boundaries but aren't part of the function's intent. Never use it to
pass regular arguments; function signatures are clearer.
HTTP request context
Every *http.Request carries a context:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // cancelled when client disconnects
result, err := queryDB(ctx, id)
// ...
}
Pass r.Context() through to every operation that could
outlive the request. When the client disconnects, everything cascades
to cancellation.
Rules & conventions
- First parameter of the function. Always name it
ctx:func DoThing(ctx context.Context, ...) error. - Don't store it in a struct unless the struct IS a request.
- Don't pass nil context. Use
context.TODO()if you don't have one. - Always call
cancel, even in timeout/deadline contexts, usually viadefer. - Check
ctx.Done()in any long-running or blocking operation.
Check your understanding
Practice exercises
Context-aware sleep
Write sleep(ctx context.Context, d time.Duration) error that sleeps for d, unless the context is cancelled first, in which case it returns ctx.Err().
Show solution
func sleep(ctx context.Context, d time.Duration) error {
select {
case <-time.After(d):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
if err := sleep(ctx, 2*time.Second); err != nil {
fmt.Println("interrupted:", err) // context deadline exceeded
}
}
Further reading
Context in hand.