The Context API contract

May 16, 2025

Today we come to the core of the context package: The Context interface itself.

type Context

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key any) any
}

A Context carries a deadline, a cancellation signal, and other values across API boundaries.

Context’s methods may be called by multiple goroutines simultaneously.

For clarity, I’ve removed all of the documentation for each of the interface methods in the above quote—we’ll get to those in following emails, and include those there.

For today, let’s just talk about the interface in general.

Because this value is an interface, anyone can create their own custom implementation. And you might be tempted to think that this means that any type you create that satisfies this interface is a valid context.Context value.

But the API contract for context.Context includes details not expressed in the type definition. In fact, most API contracts go beyond the type or function definition, so this serves as a good reminder of that fact.

In the case of Context, for example, we might think that the Err() method can return any error. I’ve even created custom Context implementations that did this. But it turns out, this breaks the API contract:

If Done is not yet closed, Err returns nil. If Done is closed, Err returns a non-nil error explaining why: DeadlineExceeded if the context’s deadline passed, or Canceled if the context was canceled for some other reason.

In other words, Err() may return three distinct values only: nil, context.DeadlineExceeded or context.Canceled. Any other return value breaks this API contract.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Get daily content like this in your inbox!

Subscribe