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:

  1. What context is and why it is used.
  2. The context.Context type and context.Background().
  3. How to cancel work, and set deadlines and timeouts.
  4. 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:

MethodGives
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 a cancel function
  • context.WithTimeout(parent, 2*time.Second) → stops after 2 seconds
  • context.WithDeadline(parent, someTime) → stops at a clock time
  • context.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
  • WithCancel returns the new context and a cancel function.
  • The worker watches ctx.Done() in a select. See Channels.
  • After cancel(), Done() is closed, and Err() is context.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 %w keeps 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 as WithDeadline(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() in main.
  • Stop by hand: WithCancel. Stop by time: WithTimeout / WithDeadline.
  • Listen: case <-ctx.Done(): and return ctx.Err().
  • Clean up: defer cancel().

Simple lesson: pass ctx into requests, goroutines and database calls, so one signal can stop them all.

TL;DR

  1. Context = a stop signal, deadline and request values that travel with work.
  2. context.Context = Done(), Err(), Deadline(), Value().
  3. Root = context.Background().
  4. Children = WithCancel, WithTimeout, WithDeadline, WithValue.

    • Cancelling a parent cancels its children.
  5. Listen = select { case <-ctx.Done(): return ctx.Err() }.

    • context.Canceled or context.DeadlineExceeded.
  6. Always = defer cancel(). ctx is the first parameter.
  7. Uses = HTTP requests, goroutines, database queries.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Not calling cancelIt is optionalAlways defer cancel()Leaked timers and goroutines.
Ignoring ctx.Done() in a long loopThe context stops the code by itselfYour code must check itWork keeps running after cancel.
Storing a context in a structContexts are settingsPass ctx as the first parameterStale, confusing contexts.
context.WithValue for normal dataIt is a handy bagOnly request-wide detailsHidden, untyped parameters.
A plain string as a value keyAny key worksUse your own key typeKeys clash between packages.
Passing nil as a contextnil 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 with WithCancel, WithTimeout, WithDeadline.
  • Cancelling a parent stops all its children.
  • Check ctx.Done() in long work, and return ctx.Err().
  • Always defer cancel(), and pass ctx first.

Next: Go Cheat Sheet. You will see the whole course on one page, for quick review.