The context package defines context.Context, an interface for deadlines, cancellation, and request-scoped values across API boundaries (package doc). Typical entry points are context.Background(), context.WithCancel, context.WithTimeout / WithDeadline, and context.WithValue. For related concurrency patterns, see goroutines and channels.
Tested with Go 1.24 on Linux.
The Context interface
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Deadline— when the context will cancel, if any.Done— closed when work should stop.Err— non-nil after cancellation (context.Canceledorcontext.DeadlineExceeded).Value— optional request data; use sparingly.
Roots: Background and TODO
context.Background() never cancels, has no deadline, and carries no values—use it as the root in main, tests, or the top of an inbound request before attaching timeouts.
context.TODO() is a placeholder when you cannot yet thread a real context; replace it once a proper parent exists.
Run the snippet: printing Background shows the empty-root context string; Err() stays nil until canceled.
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
fmt.Println(ctx)
fmt.Println("Err:", ctx.Err())
}Time limits: WithTimeout and WithDeadline
WithTimeout(parent, d) and WithDeadline(parent, t) return a child context and a cancel function. Always call cancel() (usually defer cancel()) to free the timer even if the body returns early.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
select {
case <-time.After(time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}You should see context deadline exceeded because the timeout fires before one second elapses.
Manual cancel: WithCancel
Use WithCancel when another goroutine or event should stop work (not only a clock).
package main
import (
"context"
"fmt"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch := make(chan int)
go func() {
for n := 1; ; n++ {
select {
case <-ctx.Done():
return
case ch <- n:
}
}
}()
for n := range ch {
fmt.Println(n)
if n == 3 {
cancel()
break
}
}
}You should see 1, 2, 3 printed.
Request values: WithValue
WithValue(parent, key, val) returns a child that exposes Value(key). Keys should be unexported types (for example type ctxKey int) to avoid collisions between packages—never use bare strings for library keys.
package main
import (
"context"
"fmt"
)
type ctxKey int
const sessionKey ctxKey = 1
func main() {
ctx := context.WithValue(context.Background(), sessionKey, "abc-123")
v := ctx.Value(sessionKey)
fmt.Println(v.(string))
}You should see abc-123.
Practices
- Put
ctx context.Contextfirst in function parameters. - Prefer the variable name
ctx. defer cancel()whenever you obtaincancelfromWithCancel,WithTimeout, orWithDeadline.- Avoid stuffing optional parameters into
WithValue.
Summary
The golang context package ties cancellation, timeouts, and optional request values to a Context value you pass through your call stack. Use Background (or TODO temporarily) as the root, WithTimeout / WithDeadline for time bounds, WithCancel for explicit shutdown, and WithValue only for small, request-scoped metadata—always defer cancel() when the API returns a cancel function.

