Backend Development 10 min read

Mastering Go Context Cancellation: Patterns, Best Practices, and Real-World Examples

Learn how to leverage Go's context cancellation feature with practical patterns, code examples, and best practices—including listening for cancel events, submitting cancellations, time‑based timeouts, and common pitfalls—to build faster, more robust backend services.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
Mastering Go Context Cancellation: Patterns, Best Practices, and Real-World Examples

This article explains how to use Go's context cancellation feature and presents patterns and best practices that make programs faster and more robust.

Why Cancel?

In short, cancellation prevents unnecessary work in the system.

Consider a typical HTTP server that calls a database and returns the queried data to a client.

If the client cancels the intermediate request (e.g., closes the browser), the server and database would continue working wastefully without cancellation.

Ideally, when the HTTP request stops, all downstream components should stop as well.

Context Cancellation

Now that we understand why cancellation is needed, let's see how to implement it. Because cancellation events are tightly coupled with the operation, binding them to a context is natural.

Two aspects you may want to implement:

Listen for cancellation events

Submit cancellation events

Listening for Cancellation

The context type provides a Done() method that returns a channel closed when a cancellation occurs; listening is as simple as waiting on <-ctx.Done() .

Example: an HTTP server that processes a request for two seconds and should return immediately if the request is cancelled.

<code>func main() {
    // Create an HTTP server that listens on port 8000
    http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        // This prints to STDOUT to show that processing has started
        fmt.Fprint(os.Stdout, "processing request\n")
        // Use select to execute code depending on which channel receives a message first
        select {
        case <-time.After(2 * time.Second):
            // Request processed after 2 seconds
            w.Write([]byte("request processed"))
        case <-ctx.Done():
            // Request cancelled, log it
            fmt.Fprint(os.Stderr, "request cancelled\n")
        }
    }))
}
</code>

Run the server and open localhost:8000 in a browser; closing the browser before two seconds prints "request cancelled" on the terminal.

Submitting Cancellation Events

When you have a cancellable operation, emit a cancellation via context.WithCancel , which returns a new context and a cancel function.

Consider two dependent operations; if one fails, you want to cancel the other.

<code>func operation1(ctx context.Context) error {
    // Simulate a failing operation
    time.Sleep(100 * time.Millisecond)
    return errors.New("failed")
}

func operation2(ctx context.Context) {
    select {
    case <-time.After(500 * time.Millisecond):
        fmt.Println("done")
    case <-ctx.Done():
        fmt.Println("halted operation2")
    }
}

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    go func() {
        if err := operation1(ctx); err != nil {
            cancel()
        }
    }()
    operation2(ctx)
}
</code>

Time‑Based Cancellation

Applications that must honor SLA should use time‑based cancellation. The API is similar to the previous examples, adding a timeout or deadline.

<code>// Cancel after 3 seconds (or earlier via cancel())
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)

// Cancel at a specific deadline
ctx, cancel := context.WithDeadline(ctx, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
</code>

Example: an HTTP API call to an external service with a 100 ms timeout.

<code>func main() {
    ctx := context.Background()
    ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
    req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)
    req = req.WithContext(ctx)
    client := &http.Client{}
    res, err := client.Do(req)
    if err != nil {
        fmt.Println("Request failed:", err)
        return
    }
    fmt.Println("Response received, status code:", res.StatusCode)
}
</code>

Traps and Warnings

Remember that a Go context can be cancelled only once.

If you need to report multiple errors, context cancellation may not be the best choice; use it only when you truly want to stop work.

Pass the same context instance to all functions you may want to cancel. Wrapping an already cancellable context with WithTimeout or WithCancel can lead to unexpected behavior and should be avoided.

BackendConcurrencyGoBest PracticesContextCancellation
360 Zhihui Cloud Developer
Written by

360 Zhihui Cloud Developer

360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.