Go Context Usage
context.Context carries the cancellation, deadline, and request-scoped values for a single unit of work. Pass it explicitly through the entire call chain — never store it, never replace it with Background() mid-flight, never use it as a side-channel for ordinary parameters.
Core Rules
ctx is the first parameter, named ctx context.Context. No exceptions outside interface stubs imposed by external APIs.
- Propagate the caller's
ctx all the way down. Do not start a new tree with context.Background() inside a request path.
- Do not store
Context in a struct. Pass it to each method that needs it.
- Always
defer cancel() after WithCancel/WithTimeout/WithDeadline, unless ownership is explicitly transferred.
- Context values are for request-scoped metadata only (request ID, auth principal, trace). Never for optional function parameters or config.
- Value keys must be unexported named types to prevent cross-package collisions.
Where Does Data Belong?
Pick the most explicit option that fits — context values are the last resort.
| Option |
Use for |
Why |
| Function parameter |
Anything the function needs to do its job |
Type-checked, visible at call site |
| Method receiver |
State that belongs to the type |
Already in scope |
| Package-level config |
Process-wide, immutable |
One owner, no hidden flow |
context.Value |
Request-scoped metadata that crosses layers without being a function arg |
Untyped — use sparingly |
Read references/values-and-keys.md for the unexported-key pattern, typed accessors, and OpenTelemetry/trace propagation.
Constructors
| Situation |
Use |
main, init, top-level test |
context.Background() |
| Placeholder while plumbing is incomplete |
context.TODO() |
| Inside an HTTP handler |
r.Context() |
| Need manual cancellation |
context.WithCancel(parent) |
| Need a deadline / timeout |
context.WithTimeout(parent, d) / WithDeadline |
| Background work that must outlive the request (Go 1.21+) |
context.WithoutCancel(parent) |
Propagation: The One Rule
// Bad — breaks the chain, downstream cannot be cancelled
func (s *OrderService) Create(ctx context.Context, o Order) error {
return s.db.ExecContext(context.Background(), insertSQL, o.ID)
}
// Good — same ctx flows HTTP handler -> service -> DB -> external API
func (s *OrderService) Create(ctx context.Context, o Order) error {
return s.db.ExecContext(ctx, insertSQL, o.ID)
}
Deriving and Cancelling
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() // release resources even on the happy path
select {
case <-ctx.Done():
return ctx.Err()
case res := <-doAsync(ctx):
return res
}
Read references/cancellation-and-deadlines.md for WithoutCancel, AfterFunc, and long-running goroutine cancellation patterns.
Don't Wrap Context in Custom Types
// Bad — pollutes the standard signature
type MyCtx interface {
context.Context
UserID() string
}
// Good — keep the signature standard, extract via helper
func UserIDFrom(ctx context.Context) (string, bool) { /* ... */ }
Enforce With Linters
Most context mistakes are mechanical and a linter will catch them in CI before review:
govet -vet=context — flags non-first context.Context parameters and lost cancels.
staticcheck SA1012 — calls passing nil context.
contextcheck (golangci-lint) — verifies downstream calls propagate ctx.
noctx — flags HTTP/SQL APIs called without their *Context variant.
Run golangci-lint run --enable=contextcheck,noctx,staticcheck in CI for any project that exposes context.Context.
Anti-Patterns
| Anti-pattern |
Why it hurts |
Do this instead |
ctx context.Context stored on a struct field |
Lifetime becomes invisible; outlives the request |
Pass ctx to each method |
context.Background() mid-call |
Cancellation chain breaks; goroutines leak |
Use the caller's ctx |
ctx.Value("user-id") with a string key |
Cross-package collisions, no type safety |
Unexported key type + typed getter |
Passing nil as a context |
Panics on Done() / Value() |
Use context.TODO() while plumbing |
WithTimeout without defer cancel() |
Leaks the timer until parent finishes |
defer cancel() on the next line |
Custom MyContext interface |
Breaks every standard signature |
Keep context.Context, extract with helpers |
Verification Checklist
References
1---2name: go-context-23description: Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation, request-scoped values, WithoutCancel for fire-and-forget work, and key-collision-safe value patterns. Apply proactively whenever a function takes ctx, spawns work, or accepts request-scoped data, even if the user has not asked about context.4license: MIT5---67# Go Context Usage89`context.Context` carries the cancellation, deadline, and request-scoped values for a single unit of work. Pass it explicitly through the entire call chain — never store it, never replace it with `Background()` mid-flight, never use it as a side-channel for ordinary parameters.1011## Core Rules12131. **`ctx` is the first parameter**, named `ctx context.Context`. No exceptions outside interface stubs imposed by external APIs.142. **Propagate the caller's `ctx`** all the way down. Do not start a new tree with `context.Background()` inside a request path.153. **Do not store `Context` in a struct**. Pass it to each method that needs it.164. **Always `defer cancel()`** after `WithCancel`/`WithTimeout`/`WithDeadline`, unless ownership is explicitly transferred.175. **Context values are for request-scoped metadata only** (request ID, auth principal, trace). Never for optional function parameters or config.186. **Value keys must be unexported named types** to prevent cross-package collisions.1920## Where Does Data Belong?2122Pick the most explicit option that fits — context values are the last resort.2324| Option | Use for | Why |25|---|---|---|26| Function parameter | Anything the function *needs* to do its job | Type-checked, visible at call site |27| Method receiver | State that belongs to the type | Already in scope |28| Package-level config | Process-wide, immutable | One owner, no hidden flow |29| `context.Value` | Request-scoped metadata that crosses layers without being a function arg | Untyped — use sparingly |3031> Read [references/values-and-keys.md](../../../skills/go-context/references/values-and-keys.md) for the unexported-key pattern, typed accessors, and OpenTelemetry/trace propagation.3233## Constructors3435| Situation | Use |36|---|---|37| `main`, `init`, top-level test | `context.Background()` |38| Placeholder while plumbing is incomplete | `context.TODO()` |39| Inside an HTTP handler | `r.Context()` |40| Need manual cancellation | `context.WithCancel(parent)` |41| Need a deadline / timeout | `context.WithTimeout(parent, d)` / `WithDeadline` |42| Background work that must outlive the request (Go 1.21+) | `context.WithoutCancel(parent)` |4344## Propagation: The One Rule4546```go47// Bad — breaks the chain, downstream cannot be cancelled48func (s *OrderService) Create(ctx context.Context, o Order) error {49 return s.db.ExecContext(context.Background(), insertSQL, o.ID)50}5152// Good — same ctx flows HTTP handler -> service -> DB -> external API53func (s *OrderService) Create(ctx context.Context, o Order) error {54 return s.db.ExecContext(ctx, insertSQL, o.ID)55}56```5758## Deriving and Cancelling5960```go61ctx, cancel := context.WithTimeout(ctx, 5*time.Second)62defer cancel() // release resources even on the happy path6364select {65case <-ctx.Done():66 return ctx.Err()67case res := <-doAsync(ctx):68 return res69}70```7172> Read [references/cancellation-and-deadlines.md](../../../skills/go-context/references/cancellation-and-deadlines.md) for `WithoutCancel`, `AfterFunc`, and long-running goroutine cancellation patterns.7374## Don't Wrap `Context` in Custom Types7576```go77// Bad — pollutes the standard signature78type MyCtx interface {79 context.Context80 UserID() string81}8283// Good — keep the signature standard, extract via helper84func UserIDFrom(ctx context.Context) (string, bool) { /* ... */ }85```8687## Enforce With Linters8889Most context mistakes are mechanical and a linter will catch them in CI before review:9091- `govet -vet=context` — flags non-first `context.Context` parameters and lost cancels.92- `staticcheck SA1012` — calls passing `nil` context.93- `contextcheck` (`golangci-lint`) — verifies downstream calls propagate `ctx`.94- `noctx` — flags HTTP/SQL APIs called without their `*Context` variant.9596Run `golangci-lint run --enable=contextcheck,noctx,staticcheck` in CI for any project that exposes `context.Context`.9798## Anti-Patterns99100| Anti-pattern | Why it hurts | Do this instead |101|---|---|---|102| `ctx context.Context` stored on a struct field | Lifetime becomes invisible; outlives the request | Pass `ctx` to each method |103| `context.Background()` mid-call | Cancellation chain breaks; goroutines leak | Use the caller's `ctx` |104| `ctx.Value("user-id")` with a string key | Cross-package collisions, no type safety | Unexported key type + typed getter |105| Passing `nil` as a context | Panics on `Done()` / `Value()` | Use `context.TODO()` while plumbing |106| `WithTimeout` without `defer cancel()` | Leaks the timer until parent finishes | `defer cancel()` on the next line |107| Custom `MyContext` interface | Breaks every standard signature | Keep `context.Context`, extract with helpers |108109## Verification Checklist110111- [ ] Every function that does I/O, blocks, or calls another `ctx`-aware API takes `ctx context.Context` as its **first** parameter.112- [ ] No `context.Context` field on any struct (search: `ctx\s+context\.Context` inside `type ... struct`).113- [ ] Every `WithCancel`/`WithTimeout`/`WithDeadline` is followed by `defer cancel()` on the next line.114- [ ] No `context.Background()` or `context.TODO()` calls inside request handlers.115- [ ] All context value keys are unexported named types, accessed via typed getters.116- [ ] `golangci-lint run --enable=contextcheck,noctx` passes.117118## References119120- [references/values-and-keys.md](../../../skills/go-context/references/values-and-keys.md) — unexported key types, typed accessors, trace propagation121- [references/cancellation-and-deadlines.md](../../../skills/go-context/references/cancellation-and-deadlines.md) — timeouts, `WithoutCancel`, `AfterFunc`, goroutine cancellation122- [references/http-and-db.md](../../../skills/go-context/references/http-and-db.md) — handlers, `NewRequestWithContext`, `QueryContext`/`ExecContext`