Beginners' guide to Go Contexts: The Magic Controller of Goroutines

Published: (March 6, 2026 at 04:17 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

The Basic Check

The most fundamental way to use a context is to check its state manually. This is perfect for long‑running loops or heavy calculations. If a function is a “one‑off” and finishes instantly, a context doesn’t add much value.

func process(ctx context.Context) {
    for i := range 1000000 {
        // check if the signal says we should stop
        if err := ctx.Err(); err != nil {
            fmt.Println("stopping early:", err)
            return
        }

        // simulate some work
        _ = i
    }
}

Without the if err := ctx.Err() check, the goroutine would keep spinning even if the user who started it has already disconnected or timed out.

Powering up with Select

While checking ctx.Err() works for loops, the real magic happens with the select statement. This is how you make a goroutine “listen” for a cancellation signal while it is busy doing something else, like waiting for a channel.

Waiting for a result

Imagine you are fetching data from a slow API. You want the data, but you aren’t willing to wait forever.

func fetch(ctx context.Context) {
    resultCh := make(chan string)

    go func() {
        time.Sleep(5 * time.Second) // simulate a slow task
        resultCh  **Note:** Using a **buffered channel** (`make(chan string, 1)`) is important. It ensures that if the context times out and we exit the function, the goroutine still running `OldLegacyFunction` can send its result to the channel and exit without getting stuck forever (a goroutine leak).

## The Importance of Cancel and Defer

Whenever you use `context.WithCancel`, `context.WithTimeout`, or `context.WithDeadline`, the standard library gives you back a new `context` and a `cancel` function.

**You must call that cancel function.**  
Even if your function finishes successfully, you should call it. The best way to do this is with `defer`.

```go
func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    // this ensures that when main finishes, the context is cleaned up
    defer cancel()

    doWork(ctx)
}

Why is this important?

  • Resource Cleanup: Behind the scenes, the parent context keeps track of its children. If you don’t call cancel, the parent might keep a reference to the child in memory until the parent itself dies, leading to a memory leak.
  • Stop Ongoing Work: Calling cancel() sends the signal through the ctx.Done() channel. It tells every function using that context: “The party is over, stop whatever you are doing.”
0 views
Back to Blog

Related posts

Read more »

AI, Humanity, and the Loops We Break

🌅 Echoes of Experience — Standing in the Horizon There was a time when chaos shaped me. But the moment I chose myself—truly chose myself—everything shifted. I...