# Context

Course: [Go](https://www.devtutorials.ai/learn/go) · Section: Advanced Go
Source: https://www.devtutorials.ai/learn/go/advanced-go/context

> 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:

```go
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:

| 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".

```go runnable title="The root context"
package main

import (
	"context"
	"fmt"
)

func main() {
	ctx := context.Background()
	_, hasDeadline := ctx.Deadline()
	fmt.Println(hasDeadline, ctx.Err())
}
```

```text
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

```go runnable title="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())
}
```

```text
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.

```go runnable title="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))
}
```

```text
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.

```go runnable title="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())
}
```

```text
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.

### Context Values

```go runnable title="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")
}
```

```text
[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.

```go
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.

```go runnable title="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)
}
```

```text
[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:

```go
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

| 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 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.
