In golang, the context package plays a big role in controlling the lifecycle of your code, especially when it comes to handling timeouts, cancellation signals, and passing request-scoped values.

Context Should Probably be the First Argument of your Go Functions


If you’re designing APIs, it’s a common convention to pass the context.Context (named ctx) as the first argument. Let’s talk about why.

  1. Consistency Across the Go Ecosystem: One of the core philosophies of Go is simplicity and predictability. When different libraries and applications follow the same convention, it reduces the cognitive load on developers. By always passing context as the first argument, we ensure that Go developers instantly recognize the pattern, making the codebase more readable and maintainable.

  2. Propagation of Values: The context package not only provides cancellation signals but also a way to store and retrieve values in a safe manner. By consistently passing it as the first argument, you ensure that any nested functions or methods called within have access to these values, ensuring data like request ID and other essential metadata can be seamlessly propagated. This becomes particularly important for enabling features like tracing. We discuss this further in the debugging course.

  3. Graceful Degradation During Slow or Unresponsive Service Calls: By proactively managing delays, the user experience remains controlled, avoiding potential system-wide disruptions. A well-structured context approach can be the difference between a minor issue and a major outage.

Here’s an example of a function that follows this convention:

package main

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

func SimulateProcessing(ctx context.Context) error {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("Finished processing for request ID:", ctx.Value("requestID"))
        return nil
    case <-ctx.Done():
        fmt.Println("Request ID:", ctx.Value("requestID"), " - Error:", ctx.Err())
        return ctx.Err()
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    requestID := "12345"
    ctx = context.WithValue(ctx, "requestID", requestID)

    SimulateProcessing(ctx)
}