# 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. - Published: 2026-08-29 - Updated: 2026-08-29 - Author: Anthony Fillion-Maillet - Tags: go, golang, context, concurrency, interview - Reading time: 9 min --- 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](https://pkg.go.dev/context) definieert vier methodes die elke context-implementatie moet voldoen. Het begrijpen van deze methodes vormt de basis voor effectief context-gebruik. ```go // context_interface.go 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. ```go // context_creation.go 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. ```go // cancellation.go 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. ## 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. ```go // timeouts.go 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](https://pkg.go.dev/context#WithValue) benadrukt het gebruik van custom types als keys om botsingen te voorkomen. ```go // context_values.go 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. ```go // utilities.go 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](/technologies/go/interview-questions/context-package) 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. ```go // mistakes.go 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() {} ``` ## 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/go/go-context-package-2026