Every request starts from Background() or TODO(). You then wrap it to add cancellation, deadlines, timeouts, or values.
Background vs TODO
Background is the root for normal code. TODO signals 'I don't know what to use here yet' — useful during refactors. They behave identically; the name documents intent.
ctx := context.Background() // main, init, tests
// ctx := context.TODO() // placeholder
WithCancel — caller cancels when done
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // ALWAYS defer cancel to release resources
go doWork(ctx)
time.Sleep(100 * time.Millisecond)
cancel() // tell doWork to stop
WithTimeout and WithDeadline
Timeout is relative, Deadline is absolute. Both return a cancel that you must defer, even if the deadline fires on its own.
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
ctx, cancel = context.WithDeadline(ctx, time.Now().Add(5*time.Second))
defer cancel()
WithValue — propagate request-scoped data
Use sparingly. Meant for things like request IDs, auth info, logger — NOT function arguments in disguise. Use a private key type to avoid collisions.
type reqIDKey struct{}
ctx = context.WithValue(ctx, reqIDKey{}, "req-123")
id, _ := ctx.Value(reqIDKey{}).(string)
fmt.Println(id)