context.Context: Cancellation as a First-Class API
context.Context is how Go services stop work cooperatively when a client leaves, a deadline expires, or a process shuts down.
Search across all documentation pages
context.Context is how Go services stop work cooperatively when a client leaves, a deadline expires, or a process shuts down.
It is not optional sugar in server code - it is the standard contract for timeouts, cancellation, and request-scoped metadata across HTTP handlers, database calls, gRPC, and background workers.
context Basics collects runnable snippets; sibling articles cover HTTP propagation, cross-service deadlines, values, database/gRPC integration, testing, and team conventions.
context.Context is an immutable handle passed down call stacks. It signals when work should stop and carries a small set of request-scoped values.ctx context.Context as its first parameter.database/sql, errgroup, graceful shutdown, structured logging with request IDs.Picture a request entering your service as a tree of function calls.
At the root, something creates a context - http.Request.Context() for HTTP, context.Background() for process startup, or context.WithTimeout for a bounded job.
Each downstream call receives that context (or a child derived from it).
When the root cancels - client disconnect, timeout, or explicit cancel() - every descendant that respects ctx.Done() should stop promptly.
A context carries three concerns:
Done() channel closes when work should end.Deadline().Contexts form a parent-child chain.
context.WithCancel(parent) returns a child and a cancel function.
Calling cancel() on the child does not cancel the parent, but cancelling the parent cancels all descendants.
That one-way propagation is deliberate: upstream owns the lifecycle.
The idiomatic function signature is:
func FetchUser(ctx context.Context, id string) (*User, error)Callers pass ctx unchanged or wrap it:
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()Blocking code should select on ctx.Done() or use APIs that accept context (db.QueryContext, gRPC stubs, http.NewRequestWithContext).
When ctx ends, return ctx.Err() - usually context.Canceled or context.DeadlineExceeded.
HTTP servers wire this automatically.
net/http gives each request r.Context(), which cancels when the client closes the connection or the server hits ReadHeaderTimeout/WriteTimeout boundaries.
Frameworks like chi, gin, and echo expose the same request context through their handler APIs.
google.golang.org/grpc threads metadata and deadlines through context.Context on every RPC.
database/sql methods ending in Context honor cancellation at the driver level when supported.
Shutdown paths derive a timeout context from context.Background():
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx)| Mechanism | What it signals | Typical source |
|---|---|---|
WithCancel | Manual stop | Handler exit, errgroup failure |
WithTimeout | Relative budget | Per-RPC client call |
WithDeadline | Absolute end time | Upstream grpc-timeout header |
Request.Context() | Client disconnect | net/http |
context.WithoutCancel | Detach for cleanup | Post-response logging (Go 1.21+) |
Production services stack deadlines at each hop.
An edge gateway might allow 5s; an internal service gets 3s after middleware overhead; a database query gets 500ms.
Each layer should set a deadline shorter than its parent so slack exists for serialization and retries.
controller-runtime reconcilers use context to stop work when a manager shuts down or a lease is lost.
kubebuilder-generated controllers pass ctx into client calls so API server watches respect cancellation.
golangci-lint includes checks (via staticcheck and custom linters) for context misuse - passing context.Background() inside handlers, storing context in structs, or ignoring ctx in loops.
For values, the Go team recommends unexported key types to avoid collisions:
type ctxKey int
const requestIDKey ctxKey = 1Never use context values for optional parameters that belong in function arguments.
Pair context with observability: extract request IDs from context in middleware and attach them to structured logs and traces.
| Approach | Strength | Weakness | Best Fit |
|---|---|---|---|
| Context cancellation | Standard, composable | Requires discipline in every call | HTTP/gRPC/DB stacks |
time.After per call | Simple local timeout | No propagation to children | Single goroutine scripts |
Manual done channel | Custom lifecycle | Reinvents context | Legacy code paths |
| Process signals only | OS-level stop | No per-request granularity | CLI tools |
errgroup + context | Cancels siblings on error | Needs explicit group wiring | Parallel fan-out |
r.Context().cancel() stops descendants, not ancestors.Community convention and gofmt/review tooling expect ctx context.Context first.
It keeps the signal visible at every call site.
WithCancel stops when you call cancel() or when the parent ends.
WithTimeout also sets a deadline and calls cancel automatically when time expires.
Yes for WithCancel, WithTimeout, and WithDeadline.
Deferred cancel() releases timer resources even if the deadline passed early.
Return ctx.Err() unchanged when cancellation caused the failure.
Wrap with %w only if callers need extra context while preserving errors.Is.
Methods like QueryContext pass cancellation to the driver when supported.
Always prefer *Context variants over blocking calls without context.
Deadlines and metadata ride on context.Context.
Client stubs derive timeouts from context; servers read them for enforcement.
Never - use context.Background() or context.TODO() at roots only.
Libraries should document that nil panics or is rejected.
A placeholder when the correct parent context is unclear during refactors.
Replace with a real parent before shipping.
Use unexported custom key types in each package.
String keys are a collision risk in large codebases.
No - errgroup uses context to cancel sibling goroutines on first error.
They complement each other.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026