2026-10-31·by Sijie Wang

Context

Context: cancellation, deadlines, and request data down the call tree

The motivation: done channels do not scale across layers. A done channel answers "how to stop", but a cancellation signal in a real program has to cross many layers: HTTP handler, service, repository, database driver or downstream call. Passing a done channel through every signature pollutes all of them, and it carries no deadline and no request data. Context standardizes and upgrades done: one object passed down the call chain, bundling cancellation, deadline, and request-scoped data together.

The interface has four methods. Done() returns the done channel: closed means stop. Err() says why: canceled or deadline exceeded. Deadline() reports whether a timeout point exists and when. Value(key) carries request-scoped data, such as a request ID, along the request.

Derivation is a tree, and cancellation flows one way. Start from Background and derive children with WithCancel, WithTimeout, WithDeadline, or WithValue. Canceling a parent cancels all its descendants at once, the same broadcast idea as closing a done channel, only grown into a tree. Canceling a child affects neither its parent nor its siblings. Values are immutable: deriving produces a new object and never changes the parent.

First parameter, never stored in a struct. Three reasons. Lifetimes mismatch: a context belongs to one request, while a struct often lives much longer, so storing it binds one request's cancellation to a long-lived object, or keeps using a canceled context after the request ends. The chain breaks: explicit parameters keep the cancellation path visible to every reader, while a hidden field leaves the next reader guessing whether a function can be canceled. The signature documents: a first-parameter context says at a glance that the function is cancelable and has a deadline.

WithCancel and WithTimeout return a cancel function that must be called. The convention is defer cancel(). A child that is never canceled stays registered in its parent's children until the parent is canceled or times out: that is a context leak. WithTimeout is WithDeadline set to now plus a duration; when the time arrives it cancels by itself. Err() distinguishes the cause: manual cancel gives Canceled, a reached deadline gives DeadlineExceeded.

Two usage shapes. Inside a loop, select on ctx.Done() and return ctx.Err() when it closes. After a blocking call, check Err() to tell a real error apart from cancellation or timeout. Passing down means handing the same context to calls that accept it, such as http.NewRequestWithContext or a database QueryContext: one context walks the whole chain.

Value rules. Keys use a custom unexported type so packages cannot collide on the same key. Only request-scoped metadata belongs here: request IDs, trace IDs, authentication information. Business objects do not, and Value must not smuggle in things that belong in parameters.

The don'ts. Never pass a nil context: use Background when there is none, TODO as a placeholder. Never forget the cancel function. Never store a context in a struct. Never use Value for business data or optional parameters.

The React analogy, and its limits. React Context solves prop drilling, and Go context solves done drilling: both pull something that must cross many layers out of every signature in between. Two differences. React Context carries UI state that can trigger re-renders; Go context carries control signals and request metadata, not business state. React Context is implicit, available anywhere with a hook; Go context is explicit, passed as a first parameter, flowing one way down an immutable tree.

Production note. In a web service, each request derives its context from the HTTP request context, timeouts are set at layer boundaries, and server shutdown cancels in-flight request contexts. The context is what turns "the client went away" into work that actually stops.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

Context — Sijie Wang