context Basics
10 examples to get you started with the context Package - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with the context Package - 7 basic and 3 intermediate.
mkdir ctxdemo && cd ctxdemo && go mod init example.com/ctxdemo.main.go (or separate files in one package) and run with go run ..Root contexts start chains that have no parent cancellation.
package main
import (
"context"
"fmt"
)
func main() {
fmt.Println(context.Background().Err() == nil)
fmt.Println(context.TODO().Err() == nil)
}context.Background() is the top-level empty context for main, servers, and tests.context.TODO() is a placeholder during refactors when the parent is not yet wired.Related: context.Context: Cancellation as a First-Class API - why context exists
Manual cancellation stops descendant work.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
<-ctx.Done()
fmt.Println("stopped:", ctx.Err())
}()
time.Sleep(50 * time.Millisecond)
cancel()
time.Sleep(20 * time.Millisecond)
}WithCancel returns a child context and a cancel function.defer cancel() to release resources even if you cancel early.ctx.Err() returns context.Canceled after manual cancel.Related: Cancellation Propagation in HTTP Handlers - real request lifetimes
A relative deadline cancels automatically.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
select {
case <-time.After(250 * time.Millisecond):
fmt.Println("finished work")
case <-ctx.Done():
fmt.Println("timed out:", ctx.Err())
}
}WithTimeout(parent, d) is shorthand for WithDeadline(parent, time.Now().Add(d)).Done() closes and Err() is context.DeadlineExceeded.Related: Deadlines & Timeouts Across Service Boundaries - service budgets
Absolute wall-clock end times suit upstream-provided expiry.
package main
import (
"context"
"fmt"
"time"
)
func main() {
deadline := time.Now().Add(80 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
if d, ok := ctx.Deadline(); ok {
fmt.Println("deadline:", d.Format(time.RFC3339))
}
<-ctx.Done()
fmt.Println(ctx.Err())
}Deadline() reports the cutoff and whether a deadline is set.WithTimeout when you think in durations; use WithDeadline when syncing to an external timestamp.Related: Deadlines & Timeouts Across Service Boundaries - propagating expiry
Long work must poll cancellation between iterations.
package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context) error {
for i := 0; i < 10; i++ {
select {
case <-ctx.Done():
return ctx.Err()
default:
time.Sleep(30 * time.Millisecond)
fmt.Println("tick", i)
}
}
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
fmt.Println(work(ctx))
}select with default avoids blocking when ctx is still active.ctx.Err() so callers distinguish cancel vs other failures.ctx instead of hand-rolled polling when available.Related: Testing Code That Accepts context.Context - asserting cancel paths
The standard function shape propagates lifecycle down the stack.
package main
import (
"context"
"fmt"
)
func fetch(ctx context.Context, id int) (string, error) {
if err := ctx.Err(); err != nil {
return "", err
}
return fmt.Sprintf("user-%d", id), nil
}
func handler(ctx context.Context) error {
name, err := fetch(ctx, 42)
if err != nil {
return err
}
fmt.Println(name)
return nil
}
func main() {
_ = handler(context.Background())
}ctx and place it first - reviewers and linters expect this.ctx.Err() before expensive work when calls are deep.ctx down; do not create fresh Background() mid-request.Related: context Package Best Practices - team naming rules
Typed keys carry cross-cutting data sparingly.
package main
import (
"context"
"fmt"
)
type ctxKey string
const requestIDKey ctxKey = "requestID"
func withRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
func requestID(ctx context.Context) string {
v, _ := ctx.Value(requestIDKey).(string)
return v
}
func main() {
ctx := withRequestID(context.Background(), "req-abc")
fmt.Println(requestID(ctx))
}Related: context Values: When and When Not - value discipline
Cancellation flows parent to child only.
package main
import (
"context"
"fmt"
)
func main() {
parent, parentCancel := context.WithCancel(context.Background())
defer parentCancel()
child, childCancel := context.WithCancel(parent)
defer childCancel()
childCancel()
fmt.Println("parent err:", parent.Err())
fmt.Println("child err:", child.Err())
}childCancel() sets child.Err() to context.Canceled.parentCancel() runs.Related: Context Misuse Anti-Patterns - lifetime mistakes
Each layer should subtract overhead from the remaining budget.
package main
import (
"context"
"fmt"
"time"
)
func callDownstream(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, 50*time.Millisecond)
defer cancel()
select {
case <-time.After(200 * time.Millisecond):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func main() {
parent, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
fmt.Println(callDownstream(parent))
}DeadlineExceeded at the layer that set the shortest timeout for easier debugging.Related: Deadlines & Timeouts Across Service Boundaries - hop budgets
Attach a reason to cancellation for clearer errors.
package main
import (
"context"
"errors"
"fmt"
)
func main() {
ctx, cancel := context.WithCancelCause(context.Background())
defer cancel(nil)
cause := errors.New("upstream validation failed")
cancel(cause)
fmt.Println(context.Cause(ctx))
}WithCancelCause pairs with context.Cause(ctx) for observability.ctx.Err() at API boundaries unless callers need the cause.Related: Testing Code That Accepts context.Context - asserting cancel causes
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026