🎒 The Context Package: Your Backpack for Go Adventures
The Magic Backpack Story
Imagine you’re going on a treasure hunt with your friends. Before you leave, your mom gives you a special backpack with important things inside:
- ⏰ A timer that tells you when to come home for dinner
- 📝 A note with the secret password
- 🚨 A walkie-talkie to call you back if needed
This backpack is Go’s Context Package! It carries important information and signals between different parts of your program—like passing notes between friends during a treasure hunt.
🎯 What You’ll Learn
graph TD A["Context Package"] --> B["Overview"] A --> C["Creation Functions"] A --> D["Cancellation"] A --> E["Values"] B --> B1["What is Context?"] C --> C1["Background & TODO"] C --> C2["WithCancel"] C --> C3["WithDeadline"] C --> C4["WithTimeout"] D --> D1["How Cancellation Works"] D --> D2["Done Channel"] E --> E1["WithValue"] E --> E2["Getting Values"]
📦 Context Package Overview
What is Context?
Think of context as a messenger bag that travels with your code. When you call a function, you hand it the bag. That function can:
- Look inside for important info
- Check if it’s time to stop working
- Pass the bag to other functions it calls
Real Example:
import "context"
func main() {
// Create a basic backpack
ctx := context.Background()
// Now pass it to functions
doWork(ctx)
}
func doWork(ctx context.Context) {
// I have the backpack now!
// I can check for signals
}
Why Do We Need Context?
Imagine you ask your friend to find a toy. But then:
- 🍕 Pizza arrives and you don’t need the toy anymore
- ⏰ It’s taking too long
- 🏠 You need to go home
You need a way to tell your friend: “Stop looking! Come back!”
That’s exactly what context does for your Go programs.
🔧 Context Creation Functions
1. Background() - The Empty Backpack
This is your starting point. Like an empty backpack before you pack anything.
ctx := context.Background()
When to use:
- Main function
- Starting point of your app
- When you don’t have a context yet
2. TODO() - The “Figure It Out Later” Backpack
When you’re not sure what context to use yet.
ctx := context.TODO()
When to use:
- You’re still building your code
- You’ll add proper context later
💡 Tip:
TODO()is like a sticky note saying “fix this later”
3. WithCancel() - The Backpack with a Stop Button
This creates a backpack with a red button. Press it, and everyone with that backpack knows to stop!
// Create backpack with stop button
ctx, cancel := context.WithCancel(
context.Background(),
)
// Give backpack to worker
go doWork(ctx)
// Later: Press the stop button!
cancel()
graph TD A["Parent Context"] -->|WithCancel| B["Child Context"] B --> C["cancel function"] C -->|When called| D["Child gets signal to STOP"]
4. WithDeadline() - The Backpack with an Alarm Clock
Set a specific time when work must stop.
// Stop at exactly 3:00 PM
deadline := time.Date(
2024, 1, 1, 15, 0, 0, 0,
time.Local,
)
ctx, cancel := context.WithDeadline(
context.Background(),
deadline,
)
defer cancel() // Always clean up!
Real Life: Like saying “Be home by 6 PM sharp!”
5. WithTimeout() - The Backpack with a Countdown Timer
Set how long until work must stop.
// Stop after 5 seconds
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
// Do work that must finish in 5 sec
result := fetchData(ctx)
Real Life: Like a game timer—you have 5 minutes to finish!
graph LR A["Start"] -->|5 seconds| B["Timeout!"] A --> C["Working..."] C --> D{Done before timeout?} D -->|Yes| E["Success!"] D -->|No| B
🚨 Context Cancellation
The Done() Channel - Your Walkie-Talkie
Every context has a Done() channel. It’s like a walkie-talkie that beeps when it’s time to stop.
func doWork(ctx context.Context) {
for {
select {
case <-ctx.Done():
// Walkie-talkie beeped!
// Time to stop
fmt.Println("Stopping work!")
return
default:
// Keep working
fmt.Println("Still working...")
time.Sleep(1 * time.Second)
}
}
}
Why Cancellation Matters
func main() {
ctx, cancel := context.WithTimeout(
context.Background(),
3*time.Second,
)
defer cancel()
go doWork(ctx)
// Wait for timeout
time.Sleep(5 * time.Second)
}
Output:
Still working...
Still working...
Still working...
Stopping work! ← After 3 seconds
The Err() Method - What Happened?
After context is done, check why it stopped:
err := ctx.Err()
if err == context.Canceled {
// Someone pressed cancel button
fmt.Println("Work was canceled")
}
if err == context.DeadlineExceeded {
// Timer ran out
fmt.Println("Ran out of time!")
}
🏷️ Context Values
WithValue() - Putting Notes in the Backpack
Sometimes you need to pass extra information through your code. Like putting a secret note in the backpack.
// Put a note in the backpack
type userKey string
ctx := context.WithValue(
context.Background(),
userKey("userID"),
"user-123",
)
// Pass backpack to functions
processRequest(ctx)
Getting Values Back
func processRequest(ctx context.Context) {
// Look for the note
userID := ctx.Value(userKey("userID"))
if userID != nil {
fmt.Println("User:", userID)
}
}
⚠️ Important Rules for Values
graph TD A["Context Values"] --> B["DO ✅"] A --> C[DON'T ❌] B --> B1["Request-scoped data"] B --> B2["User IDs, trace IDs"] B --> B3["Authentication tokens"] C --> C1["Function parameters"] C --> C2["Optional settings"] C --> C3["Large data objects"]
Good Example:
// Request ID for logging
ctx = context.WithValue(
ctx,
requestIDKey,
"req-abc-123",
)
Bad Example:
// DON'T DO THIS!
// Use function parameters instead
ctx = context.WithValue(
ctx,
"config",
entireConfigObject,
)
🎮 Putting It All Together
Here’s a complete example showing everything:
package main
import (
"context"
"fmt"
"time"
)
type requestID string
func main() {
// 1. Create context with timeout
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
// 2. Add a value
ctx = context.WithValue(
ctx,
requestID("reqID"),
"REQ-001",
)
// 3. Start work
go worker(ctx)
// 4. Wait a bit, then cancel
time.Sleep(2 * time.Second)
cancel()
time.Sleep(1 * time.Second)
}
func worker(ctx context.Context) {
// Get the request ID
reqID := ctx.Value(requestID("reqID"))
fmt.Printf("[%s] Starting\n", reqID)
for i := 1; ; i++ {
select {
case <-ctx.Done():
fmt.Printf("[%s] Stopped: %v\n",
reqID, ctx.Err())
return
default:
fmt.Printf("[%s] Working %d\n",
reqID, i)
time.Sleep(500 * time.Millisecond)
}
}
}
Output:
[REQ-001] Starting
[REQ-001] Working 1
[REQ-001] Working 2
[REQ-001] Working 3
[REQ-001] Working 4
[REQ-001] Stopped: context canceled
🌟 Key Takeaways
| Function | What It Does | Real-Life Example |
|---|---|---|
Background() |
Empty starting context | Empty backpack |
TODO() |
Placeholder context | “Fix later” note |
WithCancel() |
Can be manually stopped | Stop button |
WithDeadline() |
Stops at exact time | “Home by 6 PM” |
WithTimeout() |
Stops after duration | Game timer |
WithValue() |
Carries data | Secret note |
🎯 Remember This!
Context is like a backpack 🎒
- You pack it at the start
- Pass it to every function
- Everyone can check if it’s time to stop
- It can carry small notes (values)
- When canceled, everyone knows to stop
The Golden Rule: Always pass context as the first parameter of functions that need it!
func doSomething(ctx context.Context, ...) {
// ctx is ALWAYS first!
}
Now you’re ready to use Go’s Context Package like a pro! 🚀
