context.Context: Done() Channel, Parent-Child Tree Cancellation
- Last updated
- Prerequisites:
- Go goroutines and channels
- Understanding of cancellation and timeouts
- go
- context
- cancellation
- deadline
- propagation
- goroutine
Read at your depth
The practical view
context.Context carries cancellation, deadlines, and request-scoped values across API boundaries. Create with context.Background() (root), context.WithCancel(parent), context.WithTimeout(parent, d), context.WithDeadline(parent, t), context.WithValue(parent, key, val). Pass ctx as the FIRST parameter to functions that do work. Check cancellation: select { case <-ctx.Done(): ... } or ctx.Err(). Call the cancel function (defer cancel()) to release resources. Standard library APIs (http.Request.Context(), database/sql, net) honor context cancellation automatically.
The same idea in other frameworks
N/A — Go runtime/library concern.N/A — Go concern.N/A — Go concern.N/A — Go concern.Legacy vs modern
Manual goroutine stop flag vs context cancellation
A hand-rolled stop channel only handles one level and leaks on forget; context cascades cancellation through the whole tree and integrates with the standard library.
stop := make(chan struct{}) // ❌ OLD: one-off signal, no hierarchyctx, cancel := context.WithCancel(context.Background()) // ✅ NEW
defer cancel()
Interview gotchas
Context
This tests the tree model — interviewers want the parent→child propagation and the resource-leak consequence of forgetting cancel(), which is a common production bug.
The mechanical answer
Derived contexts (WithCancel/WithTimeout/WithDeadline) are child nodes: each registers itself in its parent's children map. When cancel() is called on a context, it closes that context's done channel (once, guarded by a mutex), sets the err, and recursively cancels every registered child — so the signal flows down the entire subtree. Because children unregister themselves from the parent when they are cancelled, forgetting to call cancel() leaves the child registered: the parent retains a reference to the child (and its resources — timers, goroutines) for as long as the parent lives. In a long-lived root context, un-cancelled children accumulate — the classic context leak. The rule 'defer cancel()' exists precisely to unregister and release immediately.
Trap
The naive answer is 'calling cancel is optional if the work finishes'. The child stays registered on the parent until cancelled — a slow leak in long-lived processes. Another trap: using context.WithTimeout without storing the cancel (withCancel(ctx, ...) style misreads) — the timer also holds the child until it fires. Candidates who describe the children map and the unregister-on-cancel behavior show real understanding.
Context
This tests the values mechanism and its design constraints — interviewers want the key-type guidance and the 'context is for request-scoped data, not parameters' rule.
The mechanical answer
context.WithValue attaches a key/value pair to the context for REQUEST-SCOPED data that flows across API boundaries — e.g. a request ID, a tracing span, an authenticated user. The rules: the key must be a custom, unexported type (or a type you own) to prevent collisions with other packages; the value must be safe for concurrent use (contexts are shared across goroutines); and values should be few. It is discouraged as a general parameter-passing mechanism because it's opaque (no compile-time checking), hides dependencies, and makes functions harder to test — prefer explicit parameters for the data a function genuinely needs. The lookup is a chain walk (valueCtx forwards to the parent), so deep chains are slightly slower. context.WithValue never cancels; it's purely a value carrier.
Trap
The naive answer is 'use context for any shared data'. It's for request-scoped metadata crossing boundaries, not business parameters — overuse hides dependencies and breaks readability. Another trap: using a string or a built-in type as the key — two packages could collide; the convention is an unexported type per package. Candidates who state the key-type rule and the scope guidance show real understanding.