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.

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.
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.
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.
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.
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.
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.
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.
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.
// 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.
// 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.
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 naWithTimeout,WithDeadlineofWithCancelom resource leaks te voorkomen - Gebruik
WithCancelCauseencontext.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
WithValueom botsingen tussen packages te voorkomen - Pas
WithoutCanceltoe voor cleanup operaties die moeten voltooien ongeacht parent cancellation - Geef de voorkeur aan
context.Background()voor applicatie-initialisatie encontext.TODO()alleen tijdens refactoring
Zie jij de bug in Go?
Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Geschreven door
Anthony Fillion-MailletOprichter 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
Delen
Gerelateerde artikelen

Top 25 Go-sollicitatievragen: complete gids voor ontwikkelaars
Beheers Go-sollicitatiegesprekken met de 25 meest gestelde vragen. Goroutines, channels, interfaces en concurrency-patronen met codevoorbeelden.

Go Technisch Interview: Goroutines, Channels en Concurrency [2026]
Go interviewvragen over goroutines, channels en concurrency-patronen. Codevoorbeelden en antwoorden voor technische Go-interviews.

Concurrency in Go: Goroutines en Channels - Complete Gids
Beheers concurrency in Go met goroutines en channels. Geavanceerde patronen, synchronisatie, select-statements en best practices met gedetailleerde codevoorbeelden.