Go Context & Cancellation
context.Context — Cancellation, Deadlines, Values
What: The standard mechanism for propagating cancellation, deadlines, and request-scoped values down a call tree. Cooperative: work stops only where code checks ctx.Done().
Syntax/Signature: ctx, cancel := context.WithCancel(parent); WithTimeout(parent, d); WithDeadline(parent, t); WithCancelCause + context.Cause(ctx) (1.20); WithoutCancel, AfterFunc (1.21). Always defer cancel().
func fetchAll(ctx context.Context, urls []string) error {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel() // releases timer + subtree even on success
g, ctx := errgroup.WithContext(ctx)
for _, u := range urls {
g.Go(func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil { return err }
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
return resp.Body.Close()
})
}
return g.Wait()
}
How it works under the hood: Contexts form a tree; each cancelCtx registers itself with the nearest cancellable ancestor. Done() returns a lazily-created channel that cancellation closes — that's why one cancel signals any number of waiters. Cancelling a parent walks and cancels all children. Timer contexts wrap a time.Timer that fires cancel(DeadlineExceeded). Not calling cancel keeps the child (and its timer) reachable from the parent → memory/timer leak until the parent dies.
Interview angle: "Does cancelling a context kill the goroutine?" — No. Nothing kills a goroutine from outside; the goroutine must select on ctx.Done() and return. Also: "context in struct or parameter?" — parameter, first position, named ctx (structs pin one lifetime to another and break the tree).
Gotchas / common wrong answers:
- Forgetting
defer cancel()—go vetflags it; leaks child contexts. - Stuffing dependencies (loggers, DB handles) into
ctx.Value— values are for request-scoped cross-cutting data (trace IDs, auth), not DI. See [[SOLID Principles]]'s Dependency Inversion for what DI actually means, and [[Go Embedding & Dependency Injection]] for Go's idiomatic constructor-injection alternative. - Checking
ctx.Err()without also making blocking calls cancellable — a blockedch <- vignores context unless it's inside aselectwithctx.Done()(see [[Go Channels]]).
Trade-offs / when NOT to use: Detached background work spawned from a request should use context.WithoutCancel(ctx) (keeps values/trace, drops cancellation) — passing context.Background() loses tracing.
Interview Drills
- Implement "call fetch with a 2s timeout" so that no goroutine leaks even when the timeout fires.
func fetchWithTimeout(ctx context.Context) (Data, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
ch := make(chan Data, 1) // buffered: the send below can never block
go func() { ch <- fetch() }()
select {
case d := <-ch:
return d, nil
case <-ctx.Done():
return Data{}, ctx.Err()
}
}
The buffer of 1 is the load-bearing detail: on timeout the abandoned goroutine still completes its send, exits, and the channel+value are GC'd. With an unbuffered channel it would park forever (see [[Go Goroutine Leaks]]). Even better: make fetch itself take ctx so the work stops, not just the wait.
- Why doesn't cancelling a context actually kill the goroutine that's supposed to stop? — Go has no external mechanism to terminate a goroutine — cancellation is purely cooperative signaling via a closed channel (
Done()); the goroutine itself must be written toselecton that channel and return. A goroutine that never checksctx.Done()(or is blocked on a channel op with no ctx-awareselect) simply ignores cancellation entirely. - Why would you use
context.WithoutCancel(ctx)for a background task spawned from a request handler? — The task needs to keep running after the request itself completes/is cancelled (e.g., a fire-and-forget audit log write), but should still carry the request's trace ID and other values for observability —WithoutCancelpreserves the value chain while detaching from the parent's cancellation signal, unlikecontext.Background()which loses both.
Cross-links
[[Go Channels]] · [[Go Goroutine Leaks]] · [[Go Embedding & Dependency Injection]] · [[SOLID Principles]]