Intermediate · Concurrency: goroutines, channels and context · topic 30 of 46 · 35 min
context: timeouts and cancellation
Explanation
The problem
A user opens a page; your handler calls a database and two other services. The user closes the tab after one second. Without a way to say "stop", all that work keeps running for nothing, using connections and CPU. Or a bank API hangs, and your request waits forever. You need a way to say "give up after 2 seconds" or "the caller is gone, stop now", across every function and goroutine involved. That is context.
Creating contexts
| Function | Use |
|---|---|
| context.Background() | The empty root context, in main, tests and start-up code |
| context.TODO() | A placeholder when you are not sure yet which context to use |
| context.WithCancel(parent) | Returns ctx and cancel(); calling cancel stops ctx and its children |
| context.WithTimeout(parent, d) | Cancelled automatically after duration d |
| context.WithDeadline(parent, t) | Cancelled automatically at time t |
| context.WithValue(parent, key, val) | Carries request-scoped data such as a request ID (use sparingly) |
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // ALWAYS: releases resources even if work finishes early
err := chargeCard(ctx, 49900)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("bank too slow, try again")
}Listening for cancellation
ctx.Done() returns a channel that is closed when the context is cancelled or its deadline passes. ctx.Err() then says why: context.Canceled or context.DeadlineExceeded. Long-running code checks Done in a select.
func chargeCard(ctx context.Context, paise int64) error {
result := make(chan error, 1)
go func() {
time.Sleep(3 * time.Second) // pretend slow bank call
result <- nil
}()
select {
case err := <-result:
return err
case <-ctx.Done():
return ctx.Err() // context.DeadlineExceeded or context.Canceled
}
}func processAll(ctx context.Context, ids []int) error {
for _, id := range ids {
if err := ctx.Err(); err != nil {
return err // stop between items once cancelled
}
process(id)
}
return nil
}Contexts form a tree
Every derived context has a parent. Cancelling a parent cancels all its children, but cancelling a child does not affect the parent. A child's deadline can be shorter than its parent's, never longer: the earliest deadline wins.
Where contexts come from in real code
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // cancelled if the client disconnects
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
row := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", 42)
// ...
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.example.com/rates", nil)
// resp, err := client.Do(req)
_ = row
_ = req
}The rules
- Pass ctx as the first parameter, named ctx: func Charge(ctx context.Context, amount int64) error.
- Do not store a context in a struct field; pass it to each call that needs it.
- Always call cancel, usually with defer, right after creating a cancellable context.
- Never pass a nil context. Use context.Background() or context.TODO().
- Use WithValue only for request-scoped data that crosses API boundaries (request ID, auth info), never for optional function parameters. Use your own unexported key type to avoid clashes.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
context: timeouts and cancellation · 1/7
Some work must stop: when it takes too long, or when the user leaves. context carries that stop signal.
Prefer a video by a teacher? Find free videos on “golang context withtimeout withcancel tutorial” on YouTube ↗
In real life: A train-search API that answers within 2 seconds
A travel app searches three partner APIs for train availability. The whole search must answer within 2 seconds, even if one partner hangs. A context with a 2-second timeout is passed to every partner call; slow partners are cut off, and the app shows whatever results arrived in time.
package main
import (
"context"
"fmt"
"time"
)
func search(ctx context.Context, partner string, delay time.Duration) (string, error) {
select {
case <-time.After(delay): // pretend network call
return partner + ": 12 seats", nil
case <-ctx.Done():
return "", fmt.Errorf("%s: %w", partner, ctx.Err())
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
delays := map[string]time.Duration{"A": 500 * time.Millisecond, "B": 3 * time.Second}
for _, p := range []string{"A", "B"} {
res, err := search(ctx, p, delays[p])
if err != nil {
fmt.Println("skipped:", err)
continue
}
fmt.Println(res)
}
}Practice (0/4 done)
Where should ctx go in a function signature?
What does ctx.Err() return after a WithTimeout context times out?
Which line is missing? ctx, cancel := context.WithTimeout(context.Background(), time.Second) result, err := fetch(ctx)
Write func countSlowly(ctx context.Context, n int) (int, error) that counts from 1 to n, sleeping 100 ms per step, and stops early with ctx.Err() if the context is cancelled. Call it with a 350 ms timeout and print what it returns.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Channels: passing data between goroutinesConcurrency: goroutines, channels and context · same module
- Calling other APIs safely with the HTTP clientStandard library essentials
- launch, async and structured concurrencyKotlin and Android App Development · Coroutines and Flow
- Loading, errors and slow networksFull-Stack Development · Connecting frontend and backend
- fetch: talking to serversJavaScript Programming (language in depth) · Asynchronous JavaScript
- An HTTP server with net/httpStandard library essentials
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What happens to child contexts when the parent is cancelled?
Answer: They are cancelled too
Cancellation flows down the tree.
Which context does an HTTP handler normally start from?
Answer: r.Context()
r.Context() is cancelled when the client goes away or the server shuts down.
What is context.WithValue meant for?
Answer: Request-scoped data like a request ID
Keep values small and request-scoped; pass normal parameters explicitly.
Can a child context have a later deadline than its parent?
Answer: No, the earlier deadline always wins
A child is cancelled no later than its parent.
Is an http.Client safe to use from many goroutines?Related
Answer: Yes
Clients and Transports are safe for concurrent use.
What is a goroutine leak?Related
Answer: A goroutine blocked forever that never ends, holding memory
Leaked goroutines pile up and waste memory over time.
What does launch return?Related
Answer: A Job
A Job lets you join or cancel the coroutine.
How do you cancel the notes request when the user leaves the page in React?Related
Answer: Create an AbortController, pass its signal to fetch, and call abort() in the useEffect cleanup
The cleanup runs when the component leaves the screen; abort() cancels the fetch, which rejects with an AbortError you can ignore.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Concurrency: goroutines, channels and context”. The module exam comes next.
- Module 6 exam: Concurrency · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Standard library essentials. 46 topics left in the course before the final exam.