Go Context Package in 2026: Cancellation, Timeouts en Interviewvragen

Uitgebreide handleiding voor het Go context package voor cancellation, deadlines en request-scoped values. Praktische voorbeelden en veelgestelde interviewvragen.

Go Context Package Handleiding voor Cancellation en Timeouts

Het Go context package biedt het standaardmechanisme voor het transporteren van deadlines, cancellation-signalen en request-specifieke waarden over API-grenzen heen. Elke productie Go-applicatie gebruikt context: HTTP-handlers, database-queries, gRPC-calls en background workers zijn er allemaal van afhankelijk voor graceful shutdown en timeout-beheer.

Interview Essentieel

Context is een van de meest gevraagde onderwerpen in Go-interviews. Interviewers verwachten dat kandidaten context-propagation kunnen uitleggen, correcte cancellation-handling kunnen demonstreren en veelvoorkomende valkuilen zoals het opslaan van context in structs kunnen vermijden.

De Context Interface en de Vier Methodes

De Context interface definieert vier methodes die elke context-implementatie moet voldoen. Het begrijpen van deze methodes vormt de basis voor effectief context-gebruik.

context_interface.gogo
type Context interface {
    // Deadline returns the time when work should be canceled
    Deadline() (deadline time.Time, ok bool)
    
    // Done returns a channel that closes when the context is canceled
    Done() <-chan struct{}
    
    // Err returns the reason why Done was closed
    Err() error
    
    // Value returns the value associated with key, or nil
    Value(key any) any
}

Done() retourneert een receive-only channel. Wanneer dit channel sluit, ontvangt elke goroutine die erop luistert onmiddellijk het signaal. Het pattern <-ctx.Done() blokkeert tot cancellation optreedt. Err() legt uit waarom: ofwel context.Canceled bij expliciete cancellation, of context.DeadlineExceeded wanneer een timeout of deadline is verstreken.

Contexts Maken met Background en TODO

Twee functies maken root-contexts: context.Background() en context.TODO(). Beide retourneren non-nil, lege contexts die nooit worden gecanceld.

context_creation.gogo
package main

import (
    "context"
    "log"
    "net/http"
)

func main() {
    // Background: the root context for your application
    ctx := context.Background()
    
    // Use it as parent for derived contexts
    server := &http.Server{Addr: ":8080"}
    
    // TODO: placeholder when context source is unclear
    // Static analysis tools can flag context.TODO() for review
    processLegacyData(context.TODO())
}

func processLegacyData(ctx context.Context) {
    // Context parameter enables future cancellation support
    log.Println("Processing data...")
}

Background() dient als parent voor inkomende requests, main-functies en initialisatiecode. TODO() fungeert als placeholder tijdens refactoring wanneer de juiste context-bron nog niet is bepaald. Statische analysetools kunnen TODO()-gebruik markeren voor latere review.

Cancellation met WithCancel en WithCancelCause

Handmatige cancellation stelt parent-goroutines in staat om aan child-goroutines te signaleren dat het werk moet stoppen. Dit pattern verschijnt in worker pools, background tasks en graceful shutdown-implementaties.

cancellation.gogo
package main

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

func worker(ctx context.Context, id int, results chan<- int) {
    for {
        select {
        case <-ctx.Done():
            // Check why cancellation occurred
            if cause := context.Cause(ctx); cause != nil {
                fmt.Printf("Worker %d stopped: %v\n", id, cause)
            }
            return
        default:
            // Simulate work
            time.Sleep(100 * time.Millisecond)
            results <- id * 10
        }
    }
}

func main() {
    // WithCancelCause provides error context
    ctx, cancel := context.WithCancelCause(context.Background())
    results := make(chan int, 10)
    
    // Start 3 workers
    for i := 1; i <= 3; i++ {
        go worker(ctx, i, results)
    }
    
    // Collect some results
    for i := 0; i < 5; i++ {
        fmt.Println("Result:", <-results)
    }
    
    // Cancel with a specific reason
    cancel(errors.New("shutdown requested by user"))
    
    // context.Cause retrieves the cancellation reason
    time.Sleep(50 * time.Millisecond)
    fmt.Println("Cause:", context.Cause(ctx))
}

WithCancelCause, geïntroduceerd in Go 1.20, biedt rijkere foutinformatie dan gewone WithCancel. De functie context.Cause() haalt de fout op die aan de cancel-functie is doorgegeven, wat debugging in complexe systemen vergemakkelijkt.

Klaar om je Go gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Timeouts en Deadlines voor Request Handling

HTTP-handlers, database-calls en externe API-requests moeten altijd timeouts hebben. Het context package biedt WithTimeout en WithDeadline hiervoor. Beide maken contexts die automatisch cancelen wanneer de tijd verstrijkt.

timeouts.gogo
package main

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

// fetchFromAPI simulates an HTTP call
func fetchFromAPI(ctx context.Context, endpoint string) (string, error) {
    // Simulate variable response time
    responseTime := time.Duration(100+endpoint[0]%150) * time.Millisecond
    
    select {
    case <-time.After(responseTime):
        return fmt.Sprintf("Response from %s", endpoint), nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

func main() {
    // WithTimeout: relative duration
    ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
    defer cancel() // Always call cancel to release resources
    
    result, err := fetchFromAPI(ctx, "api.example.com/users")
    if err != nil {
        if err == context.DeadlineExceeded {
            fmt.Println("Request timed out")
        } else {
            fmt.Println("Request canceled")
        }
        return
    }
    fmt.Println(result)
    
    // WithDeadline: absolute time
    deadline := time.Now().Add(500 * time.Millisecond)
    ctx2, cancel2 := context.WithDeadline(context.Background(), deadline)
    defer cancel2()
    
    result2, _ := fetchFromAPI(ctx2, "api.example.com/orders")
    fmt.Println(result2)
}

Het defer cancel() pattern zorgt ervoor dat resources worden vrijgegeven, zelfs wanneer de operatie voor de timeout voltooit. Het overslaan van deze aanroep veroorzaakt een resource leak: de timer-goroutine van de context blijft actief tot de timeout verloopt.

Request-Scoped Values met WithValue

WithValue koppelt request-specifieke data aan een context. Veelvoorkomende use cases zijn request IDs, authenticatie-tokens en tracing spans. De officiële documentatie benadrukt het gebruik van custom types als keys om botsingen te voorkomen.

context_values.gogo
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
)

// Define custom key types to avoid collisions
type contextKey string

const (
    requestIDKey contextKey = "requestID"
    userIDKey    contextKey = "userID"
)

// middleware adds request ID to context
func requestIDMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestID := r.Header.Get("X-Request-ID")
        if requestID == "" {
            requestID = generateRequestID()
        }
        
        // Create new context with request ID
        ctx := context.WithValue(r.Context(), requestIDKey, requestID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// handler retrieves values from context
func handler(w http.ResponseWriter, r *http.Request) {
    requestID, ok := r.Context().Value(requestIDKey).(string)
    if !ok {
        requestID = "unknown"
    }
    
    log.Printf("[%s] Processing request", requestID)
    fmt.Fprintf(w, "Request ID: %s", requestID)
}

func generateRequestID() string {
    return fmt.Sprintf("req-%d", time.Now().UnixNano())
}

import "time"

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)
    
    http.ListenAndServe(":8080", requestIDMiddleware(mux))
}

Context values vormen een onveranderlijke keten: elke WithValue-aanroep creëert een nieuwe context die de parent omhult. Lookups doorlopen deze keten, waardoor diep geneste waarden langzamer toegankelijk zijn. Sla alleen request-specifieke data op, geen applicatieconfiguratie of optionele parameters.

WithoutCancel en AfterFunc Utilities

Go 1.21 voegde WithoutCancel toe voor operaties die moeten voltooien ongeacht parent cancellation, zoals cleanup tasks. AfterFunc plant een callback wanneer context cancellation optreedt.

utilities.gogo
package main

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

func saveAuditLog(ctx context.Context, message string) error {
    // Use WithoutCancel: audit logs must complete even if request canceled
    cleanCtx := context.WithoutCancel(ctx)
    
    // Simulate database write
    select {
    case <-time.After(50 * time.Millisecond):
        log.Printf("Audit: %s", message)
        return nil
    case <-cleanCtx.Done():
        // This branch never executes: WithoutCancel context is never canceled
        return cleanCtx.Err()
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    
    // AfterFunc: register cleanup when context ends
    stop := context.AfterFunc(ctx, func() {
        fmt.Println("Context ended, running cleanup")
    })
    
    // Perform operation
    time.Sleep(50 * time.Millisecond)
    
    // Cancel stops the AfterFunc from running if called before context ends
    if stop() {
        fmt.Println("Cleanup prevented")
    }
    
    cancel()
    
    // Audit log completes despite parent cancellation
    saveAuditLog(ctx, "Operation completed")
}

WithoutCancel bewaart de values van de parent context maar negeert het cancellation-signaal. Dit pattern is geschikt voor logging, metrics-emissie en database commits die moeten slagen ongeacht de request-uitkomst.

Interviewvragen en Antwoorden

De voorbereiding op een technisch Go-interview vereist begrip van context internals en best practices. Deze vragen komen vaak voor in interviews bij bedrijven die Go gebruiken voor backend services. Voor meer Go interview-voorbereiding, zie de Context Package interviewvragen module.

Waarom moet context de eerste parameter zijn?

Het Go-team heeft deze conventie vastgesteld om context-propagation zichtbaar en consistent te maken. Context als eerste plaatsen signaleert aan lezers dat de functie cancellation respecteert. De standaardbibliotheek volgt dit pattern: http.Request.Context(), database/sql.QueryContext() en grpc.UnaryInterceptor verwachten allemaal context als eerste argument.

Wat gebeurt er als context in een struct wordt opgeslagen?

Het opslaan van context breekt het request lifecycle-model. Een struct kan langer bestaan dan de request waarvoor deze is gemaakt, waardoor operaties verouderde cancellation-signalen gebruiken of nieuwe deadlines missen. De context-documentatie waarschuwt expliciet tegen dit pattern.

go
// BAD: context stored in struct
type Service struct {
    ctx context.Context // Never do this
}

// GOOD: pass context to each method
type Service struct{}

func (s *Service) Process(ctx context.Context, data []byte) error {
    // Context flows through the call chain
    return s.save(ctx, data)
}

Hoe context in goroutines afhandelen?

Goroutines moeten ctx.Done() controleren in hun main loop of select statement. Het negeren van het done channel creëert goroutine leaks: de parent-functie keert terug, maar de goroutine blijft resources verbruiken.

go
// Correct pattern for long-running goroutines
func processStream(ctx context.Context, stream <-chan Data) {
    for {
        select {
        case <-ctx.Done():
            log.Println("Shutting down processor")
            return
        case data, ok := <-stream:
            if !ok {
                return
            }
            handle(data)
        }
    }
}

Wanneer context.TODO() gebruiken?

Gebruik TODO() tijdens incrementele refactoring wanneer context-ondersteuning wordt toegevoegd aan legacy code. Het markeert locaties die juiste context-propagation nodig hebben. Productiecode moet uiteindelijk alle TODO()-aanroepen vervangen door daadwerkelijke contexts van request handlers of applicatie-initialisatie.

Veelvoorkomende Fouten om te Vermijden

Onjuist context-gebruik leidt tot subtiele bugs die zich manifesteren onder load of tijdens shutdown-sequenties. Het herkennen van deze patterns voorkomt productie-incidenten.

mistakes.gogo
package main

import (
    "context"
    "time"
)

// MISTAKE 1: Ignoring context cancellation
func badWorker(ctx context.Context) {
    for {
        // Missing select on ctx.Done()
        doExpensiveWork() // Never stops when context canceled
    }
}

// MISTAKE 2: Not calling cancel
func leakyTimeout() {
    ctx, _ := context.WithTimeout(context.Background(), time.Second)
    // Timer goroutine leaks until timeout expires
    _ = ctx
}

// MISTAKE 3: Using string keys for values
func collisionProne(ctx context.Context) context.Context {
    // Different packages might use same string key
    return context.WithValue(ctx, "userID", 123) // Bad
}

// CORRECT versions
func goodWorker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            doExpensiveWork()
        }
    }
}

func properTimeout() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel() // Always release resources
    _ = ctx
}

type userIDKey struct{}

func collisionSafe(ctx context.Context) context.Context {
    return context.WithValue(ctx, userIDKey{}, 123) // Good
}

func doExpensiveWork() {}

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Belangrijkste Punten voor het Gebruik van Go Context in Productie

  • Geef context door als eerste parameter aan functies, sla het nooit op in structs
  • Roep altijd cancel() aan met defer na WithTimeout, WithDeadline of WithCancel om resource leaks te voorkomen
  • Gebruik WithCancelCause en context.Cause() voor betere error debugging in complexe cancellation chains
  • Controleer <-ctx.Done() in goroutine loops om graceful shutdown mogelijk te maken en leaks te voorkomen
  • Gebruik custom key types voor WithValue om botsingen tussen packages te voorkomen
  • Pas WithoutCancel toe voor cleanup operaties die moeten voltooien ongeacht parent cancellation
  • Geef de voorkeur aan context.Background() voor applicatie-initialisatie en context.TODO() alleen tijdens refactoring
Dagelijkse challenge

Zie jij de bug in Go?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 29 augustus 2026

Tags

#go
#golang
#context
#concurrency
#interview

Delen

Gerelateerde artikelen