Context
By Syncfusion®, Inc. · Last updated
Go's context package: context.Context and Background, cancellation, deadlines and timeouts, context values, and using context in HTTP requests, goroutines and database calls.
What Is Context?
Context = a value you pass to functions to tell them when to stop: because the work was cancelled, or because time ran out.
A context can carry:
- a cancel signal: "stop now, nobody needs this any more"
- a deadline: "stop at 10:30:00"
- a timeout: "stop after 2 seconds"
- a few request values, like the user's ID
Simple lesson: a context is a "please stop" signal that travels with your work.
A Real-World Example
Think of a school trip to the science museum.
- The teacher gives each group a return time: back at the bus by 3 PM.
- Groups split up: one to the planets hall, one to robots, one to the café.
- If it starts to rain, the teacher blows a whistle: everyone comes back at once.
- No group needs to be told separately. The whistle reaches them all.
Now the mapping:
- The return time = a deadline or timeout.
- The whistle =
cancel(). - Every group hearing it = every function and goroutine that got the context.
By custom, ctx context.Context is the first parameter of a function that uses it:
func fetchMarks(ctx context.Context, roll int) (int, error)
What You Will Learn
In this lesson, you will learn:
- What context is and why it is used.
- The
context.Contexttype andcontext.Background(). - How to cancel work, and set deadlines and timeouts.
- Common uses: HTTP requests, goroutines and database operations.
Context Fundamentals
Why Context Is Used
- Stop wasted work: a user closes the page, so stop building their report.
- Set time limits: a slow database must not freeze the whole server.
- Stop many goroutines at once: one cancel reaches every worker.
- Carry request details: like a request ID for logging.
Simple lesson: context stops work that is no longer needed or is taking too long.
Context Package
context.Context
context.Context = an interface with four methods:
| Method | Gives |
|---|---|
Done() | a channel that is closed when the context is cancelled or times out |
Err() | nil while active, then context.Canceled or context.DeadlineExceeded |
Deadline() | the deadline, and whether there is one |
Value(key) | a value stored in the context |
context.Background()
context.Background() = an empty root context. It is never cancelled and has no deadline.
- Every context tree starts from it, usually in
main. context.TODO()is the same, but says "I will pick the right context later".
The root context
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
_, hasDeadline := ctx.Deadline()
fmt.Println(hasDeadline, ctx.Err())
}
false <nil>
Building a Tree
New contexts are made from a parent:
context.WithCancel(parent)→ a child plus acancelfunctioncontext.WithTimeout(parent, 2*time.Second)→ stops after 2 secondscontext.WithDeadline(parent, someTime)→ stops at a clock timecontext.WithValue(parent, key, value)→ carries a value
Cancel a context and see which ones stop.
- Cancelling a parent cancels every child below it.
- Cancelling a child does not touch its parent or its siblings.
Simple lesson: start from context.Background(), and build child contexts that can be cancelled or timed out.
Context Operations
Cancellation
Stop a goroutine with cancel()
package main
import (
"context"
"fmt"
"time"
)
func cleaner(ctx context.Context, done chan<- string) {
rooms := 0
for {
select {
case <-ctx.Done():
done <- fmt.Sprint("stopped after ", rooms, " rooms: ", ctx.Err())
return
case <-time.After(10 * time.Millisecond):
rooms++
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
done := make(chan string)
go cleaner(ctx, done)
time.Sleep(35 * time.Millisecond)
cancel()
msg := <-done
fmt.Println(msg[:7], "...", ctx.Err())
}
stopped ... context canceled
WithCancelreturns the new context and acancelfunction.- The worker watches
ctx.Done()in aselect. See Channels. - After
cancel(),Done()is closed, andErr()iscontext.Canceled. - How many rooms it cleaned depends on timing, so we print only the start of the message.
Timeout Handling
Press Play to see a timeout stop slow work.
A 50 ms time limit
Fast work finishes, slow work is stopped
package main
import (
"context"
"errors"
"fmt"
"time"
)
func task(ctx context.Context, name string, d time.Duration) error {
select {
case <-time.After(d):
fmt.Println(name, "finished")
return nil
case <-ctx.Done():
return fmt.Errorf("%s stopped: %w", name, ctx.Err())
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Millisecond)
defer cancel()
fmt.Println(task(ctx, "print hall tickets", 5*time.Millisecond))
err := task(ctx, "build full report", time.Second)
fmt.Println(err)
fmt.Println(errors.Is(err, context.DeadlineExceeded))
}
print hall tickets finished
<nil>
build full report stopped: context deadline exceeded
true
- One timeout covers both calls: the second gets only the time that is left.
- Wrapping with
%wkeeps the cause. See Error Handling.
Deadlines
Deadline = an exact clock time to stop.
Deadline vs timeout
package main
import (
"context"
"fmt"
"time"
)
func main() {
deadline := time.Now().Add(2 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
d, ok := ctx.Deadline()
fmt.Println(ok, d.Equal(deadline))
fmt.Println(ctx.Err())
}
true true
<nil>
WithTimeout(parent, d)is the same asWithDeadline(parent, time.Now().Add(d)).ctx.Deadline()tells any function how much time it has.
The three ways to stop
Context Values
Carry a request ID
package main
import (
"context"
"fmt"
)
type ctxKey string
func logLine(ctx context.Context, msg string) {
id, _ := ctx.Value(ctxKey("requestID")).(string)
fmt.Println("[" + id + "] " + msg)
}
func main() {
ctx := context.WithValue(context.Background(), ctxKey("requestID"), "REQ-42")
logLine(ctx, "fetching marks")
logLine(ctx, "done")
}
[REQ-42] fetching marks
[REQ-42] done
- Use your own key type, like
ctxKey, so keys from different packages never clash. - Use values only for request details like IDs. Pass normal data as normal parameters.
Always call cancel, even when the work finishes early.
Write defer cancel() right after WithCancel, WithTimeout or WithDeadline. Otherwise the context's timer and resources stay alive.
Simple lesson: cancel when you choose, time out after a while, or stop at a deadline, and always defer cancel().
Common Use Cases
HTTP Requests
Every web request in Go already has a context. It is cancelled when the visitor closes the page.
func reportHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // cancelled if the browser disconnects
report, err := buildReport(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusGatewayTimeout)
return
}
fmt.Fprintln(w, report)
}
// Outgoing request with a time limit:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://example.com/results", nil)
resp, err := http.DefaultClient.Do(req)
- Network code cannot run in this browser lab, so this is a plain block.
Goroutines
One cancel stops a whole group of workers.
Stop every worker at once
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
stopped := make([]bool, 3)
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
stopped[i] = true
}()
}
time.Sleep(10 * time.Millisecond)
cancel()
wg.Wait()
fmt.Println(stopped)
}
[true true true]
- All three workers wait on the same
ctx.Done(). - One
cancel()wakes them all.
Database Operations
Database packages take a context too, so a slow query can be cut off:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
row := db.QueryRowContext(ctx, "SELECT marks FROM results WHERE roll = ?", 42)
- If the query takes longer than 2 seconds, it is stopped and returns
context deadline exceeded.
Easy way to remember
- Start:
context.Background()inmain. - Stop by hand:
WithCancel. Stop by time:WithTimeout/WithDeadline. - Listen:
case <-ctx.Done():and returnctx.Err(). - Clean up:
defer cancel().
Simple lesson: pass ctx into requests, goroutines and database calls, so one signal can stop them all.
TL;DR
- Context = a stop signal, deadline and request values that travel with work.
- context.Context =
Done(),Err(),Deadline(),Value(). - Root =
context.Background(). Children =
WithCancel,WithTimeout,WithDeadline,WithValue.- Cancelling a parent cancels its children.
Listen =
select { case <-ctx.Done(): return ctx.Err() }.context.Canceledorcontext.DeadlineExceeded.
- Always =
defer cancel().ctxis the first parameter. - Uses = HTTP requests, goroutines, database queries.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Not calling cancel | It is optional | Always defer cancel() | Leaked timers and goroutines. |
Ignoring ctx.Done() in a long loop | The context stops the code by itself | Your code must check it | Work keeps running after cancel. |
| Storing a context in a struct | Contexts are settings | Pass ctx as the first parameter | Stale, confusing contexts. |
context.WithValue for normal data | It is a handy bag | Only request-wide details | Hidden, untyped parameters. |
| A plain string as a value key | Any key works | Use your own key type | Keys clash between packages. |
Passing nil as a context | nil means "no context" | Use context.Background() | A panic. |
Check Yourself
Exercises
What to Remember
- A context carries a stop signal, a deadline and request values.
- Start with
context.Background(), and build children withWithCancel,WithTimeout,WithDeadline. - Cancelling a parent stops all its children.
- Check
ctx.Done()in long work, and returnctx.Err(). - Always
defer cancel(), and passctxfirst.
Next: Go Cheat Sheet. You will see the whole course on one page, for quick review.