Understanding Context in Go: A Beginner's Guide
What is Context?
Context is Go's way of managing cancellations, timeouts, and request-scoped values across goroutines. Since Go is not an object-oriented language, you might find context the closest way of dealing with attributes attached to a particular flow of data.
π The Context Hierarchy
context.Background() (root)
β
ββ WithTimeout βββ Auto-cancels after duration
β
ββ WithCancel βββ Manual cancellation
β
ββ WithDeadline βββ Cancels at specific time
β
ββ WithValue βββ Carries request data
Creating Contexts: Practical Examples
1οΈβ£ Background Context (Starting Point)
func main() {
ctx := context.Background()
processRequest(ctx)
}
Output: Root context with no deadline or values
2οΈβ£ WithTimeout (Auto-Cancel)
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func SimpleRandom(donn chan bool) {
// First we will take a random number
randomNum := rand.Intn(100) > 50
if !randomNum {
time.Sleep(50 * time.Second)
}
time.Sleep(time.Second)
donn <- true
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
chann := make(chan bool)
go SimpleRandom(chann)
select {
case <-chann:
fmt.Println("Ok we got the output within the deadline")
case <-ctx.Done():
fmt.Println("Timed out! No more waiting")
}
}
Let's say you are handling a CPU intensive task which takes random amount of time. Since we cannot wait indefinitely, we can use WithTimeout and wait for either channel or the context to timeout.
Output:
-
Ok we got the output within the deadlineOR Timed out! No more waiting
Use cases: OS operations, waiting for HTTP requests, and more.
Note: Always perform
defer cancel()so that any memory leaks are avoided and cleanup happens carefully.
Use Case 2: Cancellable DB Query
Suppose we are connecting to a database in our code and the database is taking a lot of time. In that scenario also, context tells the connector to exit after a certain time.
func searchUsers(ctx context.Context, name string) ([]User, error) {
query := "SELECT id, name, email FROM users WHERE name LIKE ?"
rows, err := db.QueryContext(ctx, query, "%"+name+"%")
if err != nil {
return nil, err
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
return nil, err
}
users = append(users, u)
}
return users, nil
}
// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
users, err := searchUsers(ctx, "John")
Use Case 4: Request Pipeline with Tracing
When we are dealing with webserver handlers, we often find the same data being fetched in different places to achieve our goals. To solve this, we can use the WithValue of the context to store some variables.
In the above code, the Auth handler attaches the userId to the context and the subsequent pipeline executions can utilize the same instead of querying back.
Final Verdict
β You can use context to manage the lifecycle of goroutines, prevent hangs, and enforce deadlines.
β You can also use it to pass data to the chain of execution flows, primarily in middleware.
β Never store anything that is not request scoped in the context.
Happy coding! π




Top comments (0)