Context
What is context.Context?
Section titled “What is context.Context?”context.Context is a standard interface that carries:
- A cancellation signal — a goroutine can check
ctx.Done()to learn that it should stop working - An optional deadline or timeout — work is automatically cancelled when the clock expires
- Request-scoped key/value pairs — small amounts of metadata like a request ID or authentication token
The context package solves a concrete problem: when a server request is cancelled (the user closes the browser, the upstream times out), how does every in-flight goroutine spawned for that request know to stop? Passing context.Context as the first argument to every I/O function is the Go-standard answer.
Creating contexts
Section titled “Creating contexts”// Root contexts — never carry cancellationcontext.Background() // top-level root, use in main or test setupcontext.TODO() // placeholder when you are unsure which context to use
// Derived contexts — add cancellation/timeout on top of a parentctx, cancel := context.WithCancel(parent)ctx, cancel := context.WithTimeout(parent, 5*time.Second)ctx, cancel := context.WithDeadline(parent, time.Now().Add(5*time.Second))ctx2 := context.WithValue(parent, key, value)Derived contexts form a tree. Cancelling a parent automatically cancels all its children.
The cancellation contract
Section titled “The cancellation contract”When you call context.WithCancel or context.WithTimeout, you receive two values: the derived context and a cancel function. You must call cancel() when the work is done — even if the context expires naturally. If you don’t, the parent context keeps a reference to the child until the parent is also cancelled, which leaks resources.
ctx, cancel := context.WithCancel(context.Background())defer cancel() // alwaysListening for cancellation
Section titled “Listening for cancellation”Inside a goroutine, ctx.Done() returns a channel that is closed when the context is cancelled or times out. Use select to multiplex it with real work:
select {case result := <-workCh: return result, nilcase <-ctx.Done(): return "", ctx.Err() // context.Canceled or context.DeadlineExceeded}context.WithValue
Section titled “context.WithValue”context.WithValue attaches a key/value pair to the context. Use only for request-scoped data that crosses API boundaries (request ID, auth token). Do not use it as a general-purpose parameter-passing mechanism — put those in function arguments instead.
To avoid key collisions with other packages, always use a package-private, unexported type as the key:
type contextKey stringconst requestIDKey contextKey = "requestID"package main
import ( "context" "fmt")
func fetchData(ctx context.Context, id int) (string, error) { select { case <-ctx.Done(): return "", ctx.Err() default: return fmt.Sprintf("data-%d", id), nil }}
type ctxKey string
func main() { // WithCancel: normal fetch succeeds ctx, cancel := context.WithCancel(context.Background()) defer cancel()
result, err := fetchData(ctx, 1) if err != nil { fmt.Println("error:", err) } else { fmt.Println("got:", result) }
// Cancel, then try again — now the context is done cancel() result2, err2 := fetchData(ctx, 2) if err2 != nil { fmt.Println("cancelled:", err2) } else { fmt.Println("got:", result2) }
// WithValue: attach request-scoped metadata ctx3 := context.WithValue(context.Background(), ctxKey("requestID"), "abc-123") rid := ctx3.Value(ctxKey("requestID")).(string) fmt.Println("requestID:", rid)}Loading Go runtime (first run only, ~8 MB)…