Go Cheatsheet
Context
Use this Go reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
What a Context Carries
A context.Context carries a cancellation signal, an optional deadline,
and request-scoped values across API boundaries and goroutines. It is the
standard way to say "stop, nobody wants this result anymore".
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any }
Contexts are immutable. Every With* function returns a child that inherits
the parent, and cancelling a parent cancels every descendant. Cancelling a child
never touches the parent.
ctx := context.Background() // the root, in main / init / tests ctx = context.TODO() // placeholder, "a ctx belongs here, not wired yet"
Background and TODO are identical at runtime. TODO exists so linters and
readers can see the plumbing is unfinished.
Cancel, Timeout, Deadline
// Manual cancellation ctx, cancel := context.WithCancel(parent) defer cancel() // Relative deadline ctx, cancel := context.WithTimeout(parent, 3*time.Second) defer cancel() // Absolute deadline ctx, cancel := context.WithDeadline(parent, time.Now().Add(3*time.Second)) defer cancel() deadline, ok := ctx.Deadline() // ok is false when there is none
defer cancel() is not optional. Skipping it leaks the timer and the child
context until the parent is done. go vet catches it with the lostcancel check:
the cancel function is not used on all paths (possible context leak)
Calling cancel twice is safe and does nothing the second time. Calling it after
the deadline already fired is also fine. A WithTimeout child of a parent with a
shorter deadline keeps the parent's, never the longer one.
Watching for Cancellation
Done() closes when the context is cancelled. Err() says why, and returns
nil until then.
ctx.Err() | Meaning |
|---|---|
nil | still live |
context.Canceled | someone called cancel |
context.DeadlineExceeded | the deadline passed (it also implements net.Error) |
func worker(ctx context.Context, jobs <-chan Job) error { for { select { case <-ctx.Done(): return ctx.Err() // Canceled or DeadlineExceeded case j, ok := <-jobs: if !ok { return nil } if err := handle(ctx, j); err != nil { return err } } } }
For a long CPU-bound loop with nothing to select on, poll instead:
for i := range items { if i%1000 == 0 && ctx.Err() != nil { return ctx.Err() } }
Always check ctx.Done() in the same select as any blocking channel send or
receive, otherwise the goroutine blocks forever and the cancellation is useless.
Cause, WithoutCancel, AfterFunc
// 1.20+ : attach a reason to the cancellation ctx, cancel := context.WithCancelCause(parent) cancel(errors.New("upstream returned 503")) ctx.Err() // context.Canceled, unchanged context.Cause(ctx) // the error you passed in // Timeout with a custom cause ctx, cancel := context.WithTimeoutCause(parent, time.Second, ErrTooSlow) defer cancel() // plain CancelFunc here, only WithCancelCause takes an err
Cause returns the deepest cause up the chain, so a caller can tell "the client
hung up" from "the circuit breaker tripped". For a plain WithCancel it returns
context.Canceled and for a deadline context.DeadlineExceeded.
// 1.21+ : keep the values, drop the cancellation and deadline bg := context.WithoutCancel(r.Context()) go auditLog(bg, event) // survives the handler returning // 1.21+ : run f once the ctx is done, in its own goroutine stop := context.AfterFunc(ctx, func() { conn.Close() // unblock a read that ignores context }) defer stop() // returns false if f already started
WithoutCancel is the correct answer to "my background goroutine dies when the
request ends". Do not reach for context.Background() there, you lose the trace
IDs and other request values.
Values
WithValue is for request-scoped metadata such as a request ID, a trace span,
or the authenticated user. Never for optional parameters or dependencies.
type reqIDKey struct{} // unexported type, zero-size, cannot collide func withReqID(ctx context.Context, id string) context.Context { return context.WithValue(ctx, reqIDKey{}, id) } func reqID(ctx context.Context) (string, bool) { id, ok := ctx.Value(reqIDKey{}).(string) return id, ok }
- The key must be comparable, and must be an unexported custom type. A plain
string key like "user" can be silently overwritten by any other package.
- Expose typed getter and setter helpers. Callers should never touch
ctx.Value directly.
- Lookup walks the parent chain linearly, so it is not a place for hot data.
- Values are invisible in a function signature, which is exactly why real
arguments should stay real arguments.
Propagating Through the Standard Library
Every blocking stdlib call that can be cancelled takes a context.
// HTTP out req, err := http.NewRequestWithContext(ctx, "GET", url, nil) resp, err := client.Do(req) // cancelled when ctx is // HTTP in: the server gives you one, already cancelled on disconnect func handle(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel() rows, err := db.QueryContext(ctx, "SELECT id FROM items") // ... } // database/sql db.QueryContext(ctx, q, args...) db.QueryRowContext(ctx, q, args...) db.ExecContext(ctx, q, args...) db.BeginTx(ctx, nil) tx.StmtContext(ctx, stmt) // elsewhere (&net.Dialer{}).DialContext(ctx, "tcp", addr) exec.CommandContext(ctx, "ffmpeg", args...) // kills the process on cancel os.ReadFile // no ctx, files do not cancel
Checking why a call failed:
if err != nil { switch { case errors.Is(err, context.DeadlineExceeded): return fmt.Errorf("upstream timed out: %w", err) case errors.Is(err, context.Canceled): return nil // the caller walked away, not an error default: return err } }
Use errors.Is, not ==. net/http wraps the error in a *url.Error and
database/sql wraps it too, so an equality check silently fails.
Rules
- ctx is the first parameter, named ctx, and its type is
context.Context. func Fetch(ctx context.Context, id string) (*Item, error).
- Never store a Context in a struct field. It belongs to one call, not to an
object. The exception is a struct that is a request, like http.Request.
- Never pass a nil Context. Pass context.TODO() if you have nothing better.
- Only the function that creates a cancellable context calls its cancel, and it
does so with defer.
- Do not pass a context down and also keep cancelling it from two places. One
owner.
- Contexts are safe for concurrent use by multiple goroutines.
- A context is not a request-scoped grab bag, and it is not a way to avoid adding
a parameter.