Go

Go Context Package👨‍💻

The context package is Go's standard mechanism for carrying deadlines, cancellation signals, and request-scoped values across API boundaries and between goroutines. Every function that performs I/O, launches goroutines, or calls into another service should accept a context.Context as its first parameter. This is not a convention — it is enforced by the standard library itself, from net/http to database/sql to os/exec.

Contexts form a tree. context.Background() is the root, typically created once at program startup or at the top of an HTTP handler. From there, you derive child contexts with WithCancel, WithTimeout, WithDeadline, or WithValue. When a parent context is cancelled, all its children are cancelled automatically. This propagation is the key insight: a single cancel call at the HTTP handler level tears down the entire goroutine tree spawned by that request — database queries, outbound HTTP calls, background computations — without any goroutine needing to know about the others.

WithValue attaches request-scoped metadata (request IDs, authentication tokens, trace spans) to the context. It is deliberately awkward to use — the API requires any keys and values with no type safety — because it is meant for cross-cutting concerns that transit process boundaries, not for passing function parameters. Overusing WithValue creates implicit dependencies that are invisible in function signatures and impossible to trace at compile time.

Key Takeaways

  • 1`context.Background()` is the top-level context for main functions, initialization, and tests — it is never cancelled and carries no values
  • 2`context.WithCancel` returns a derived context and a cancel function — calling cancel() signals all goroutines watching ctx.Done() to shut down immediately
  • 3`context.WithTimeout` and `context.WithDeadline` automatically cancel after a duration or at a specific time — essential for preventing runaway requests and enforcing SLAs
  • 4Cancellation propagates downward through the context tree: cancelling a parent cancels all descendants, but cancelling a child does not affect the parent or siblings
  • 5`context.WithValue` attaches request-scoped data (trace IDs, auth tokens) — keys should be unexported types to prevent collisions across packages
  • 6The `ctx.Done()` channel is closed when the context is cancelled, making it usable in select statements alongside other channel operations for cooperative shutdown

Master go context package

Take the Go Programming course with hands-on lessons and challenges.

Examples

WithCancel for manual goroutine lifecycle control

go

Three independent pollers share a context derived with WithCancel. Each goroutine selects on ctx.Done() alongside its ticker. A single cancel() call tears down all three goroutines simultaneously. ctx.Err() returns context.Canceled so each goroutine knows why it stopped.

WithTimeout for request deadlines

go

WithTimeout creates a context that automatically cancels after 500ms. The slowQuery function selects between completing its work and the context deadline. When the timeout fires, ctx.Done() closes and the function returns a wrapped context.DeadlineExceeded error. The deferred cancel() is still required to release the internal timer even if the function completes before the timeout.

Context propagation in HTTP handlers

go

The HTTP request context (r.Context()) is automatically cancelled when the client disconnects. The handler derives a child context with a 500ms timeout to bound total processing time. Both the database and API calls receive this context and abort early if the deadline passes or the client disconnects. This uses Go 1.22 enhanced ServeMux routing with path parameters.

WithValue for request-scoped metadata

go

WithValue attaches the request ID to the context so any function in the call chain can access it without threading it through every parameter list. The unexported ctxKey type ensures no other package can accidentally overwrite the value. Helper functions WithRequestID and RequestID provide a type-safe API over the untyped context.Value mechanism.

Context-aware pipeline with cancellation

go

Every pipeline stage wraps its channel send in a select with ctx.Done(). When the consumer only needs 3 values, calling cancel() unblocks all stages simultaneously. Without context, the generate goroutine would block forever trying to send values that nobody reads. This is the pattern recommended by the Go blog for building cancellation-safe pipelines.

Nested contexts with layered timeouts

go

Contexts nest naturally. The outer context imposes a 2-second request deadline. Within processRequest, the database query gets a stricter 500ms sub-deadline. If the DB query times out, it does not cancel the parent context — only the dbCtx child. But if the parent is cancelled (client disconnect), all children cancel too. This layered approach lets you enforce both per-step and overall request budgets.

Common Mistakes

Mistake:

Forgetting to call the cancel function returned by WithCancel/WithTimeout/WithDeadline — this leaks the internal goroutine that the context package creates to manage the timer

Fix:

Always `defer cancel()` immediately after creating a derived context. Even if the context expires naturally, calling cancel() is required to free resources. The cancel function is idempotent — calling it multiple times is safe.

Mistake:

Storing contexts in structs instead of passing them as function parameters — this makes cancellation scope ambiguous and breaks the context tree model

Fix:

Pass context.Context as the first parameter named ctx to every function that needs it. The Go standard library follows this convention universally. A context stored in a struct becomes stale and does not reflect the caller's lifecycle.

Mistake:

Using context.WithValue to pass required function arguments (database connections, loggers, configuration) instead of explicit parameters

Fix:

WithValue is for cross-cutting request-scoped metadata (request IDs, trace spans, auth tokens) that transits API boundaries. Required dependencies should be explicit function parameters or struct fields. If you find yourself doing ctx.Value() in business logic, refactor to pass the value directly.

Mistake:

Using context.Background() deep inside a call chain instead of propagating the caller's context — this breaks cancellation propagation so goroutines ignore client disconnects and timeouts

Fix:

Thread the context from the outermost handler through every function call. context.Background() should only appear at the program entry point (main, init) or in tests. If a library function does not accept a context, wrap it with a select on ctx.Done().

Best Practices

  • Always defer cancel() on the same line as context creation. The pattern `ctx, cancel := context.WithTimeout(parent, d); defer cancel()` should be muscle memory. Failing to cancel leaks resources even when the timeout fires.
  • Use unexported types for context value keys to prevent collisions between packages. Define `type ctxKey string` (or use an empty struct) as an unexported type, and provide exported helper functions like `WithRequestID(ctx, id)` and `RequestID(ctx)` for type-safe access.
  • Set timeouts at the edges of your system — HTTP handlers, gRPC interceptors, CLI entry points — and propagate them inward. Inner functions should not set their own timeouts unless they need a stricter sub-deadline than what the caller provides.
  • Check ctx.Err() before starting expensive operations. If the context is already cancelled when your function begins, skip the work immediately rather than starting a database query or HTTP call that will be aborted anyway.
  • Use context.AfterFunc (Go 1.21+) to register cleanup callbacks that fire when a context is cancelled, instead of spawning a goroutine that just watches ctx.Done(). This reduces goroutine count and simplifies cleanup logic.

Summary

The context package provides Go's standard mechanism for propagating deadlines, cancellation signals, and request-scoped values through a call graph. Derived contexts form a tree where cancelling a parent cancels all descendants. WithTimeout bounds operation duration, WithCancel enables manual shutdown, and WithValue carries metadata across API boundaries. Every function that does I/O or spawns goroutines should accept a context.Context as its first parameter and check ctx.Done() in select statements to enable cooperative cancellation.

Practice Go with hands-on challenges

Learn go context package hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Go with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.