0
0
GoHow-ToBeginner · 3 min read

How to Pass Context in Go: Simple Guide with Examples

In Go, you pass context.Context as the first argument to functions that need cancellation, deadlines, or request-scoped values. Use context.Background() or context.TODO() to create a root context, then pass it down through function calls.
📐

Syntax

In Go, functions that accept context take a context.Context as their first parameter. You create a root context with context.Background() or context.TODO(). You can derive new contexts with cancellation or deadlines using context.WithCancel, context.WithTimeout, or context.WithDeadline.

Example syntax:

func doWork(ctx context.Context) {
    // use ctx inside
}
go
func doWork(ctx context.Context) {
    // use ctx inside
}
💻

Example

This example shows how to create a context with cancellation and pass it to a function that listens for cancellation to stop work early.

go
package main

import (
    "context"
    "fmt"
    "time"
)

func doWork(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Work cancelled:", ctx.Err())
            return
        default:
            fmt.Println("Working...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go doWork(ctx)

    time.Sleep(2 * time.Second)
    cancel() // stop the work

    time.Sleep(1 * time.Second) // wait to see cancellation
}
Output
Working... Working... Working... Working... Work cancelled: context canceled
⚠️

Common Pitfalls

  • Not passing context.Context as the first parameter in functions that need it.
  • Ignoring the context cancellation signal and continuing work.
  • Creating new root contexts inside functions instead of passing the existing one down.
  • Using context.TODO() in production instead of proper contexts.
go
package main

import (
    "context"
    "fmt"
)

// Wrong: creating new root context inside function
func doWorkWrong() {
    ctx := context.Background() // This ignores caller's context
    select {
    case <-ctx.Done():
        fmt.Println("Cancelled")
    default:
        fmt.Println("Working")
    }
}

// Right: accept context as parameter
func doWorkRight(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Cancelled")
    default:
        fmt.Println("Working")
    }
}

func main() {
    doWorkWrong()
    doWorkRight(context.Background())
}
Output
Working Working
📊

Quick Reference

Remember these key functions from the context package:

  • context.Background(): returns an empty root context.
  • context.TODO(): placeholder context for unfinished code.
  • context.WithCancel(parent): returns a context and a cancel function to stop work.
  • context.WithTimeout(parent, duration): cancels context after timeout.
  • context.WithDeadline(parent, time): cancels context at a specific time.

Key Takeaways

Always pass context.Context as the first argument to functions needing cancellation or deadlines.
Create root contexts with context.Background() or context.TODO() and pass them down.
Use context.WithCancel, WithTimeout, or WithDeadline to control cancellation.
Check ctx.Done() channel to stop work early when context is cancelled.
Avoid creating new root contexts inside functions; always accept and pass context.