# Go Context Package 2026: Pembatalan, Timeout, dan Pertanyaan Wawancara > Kuasai package context Go untuk pembatalan, timeout, dan request-scoped values. Panduan lengkap dengan contoh kode dan pertanyaan wawancara. - Published: 2026-08-29 - Updated: 2026-08-29 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Package context Go menyediakan mekanisme standar untuk membawa deadline, sinyal pembatalan, dan request-scoped values melintasi batas API. Setiap aplikasi Go production menggunakan context: HTTP handler, query database, panggilan gRPC, dan background worker semuanya bergantung padanya untuk graceful shutdown dan manajemen timeout. > **Penting untuk Wawancara** > > Context adalah salah satu topik yang paling sering ditanyakan dalam wawancara Go. Pewawancara mengharapkan kandidat dapat menjelaskan propagasi context, mendemonstrasikan penanganan pembatalan yang benar, dan menghindari kesalahan umum seperti menyimpan context dalam struct. ## Interface Context dan Empat Metodenya [Interface Context](https://pkg.go.dev/context) mendefinisikan empat metode yang harus dipenuhi oleh setiap implementasi context. Memahami metode-metode ini membentuk dasar untuk penggunaan context yang efektif. ```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()` mengembalikan channel receive-only. Ketika channel ini ditutup, setiap goroutine yang mendengarkannya menerima sinyal secara langsung. Pola `<-ctx.Done()` memblokir hingga pembatalan terjadi. `Err()` menjelaskan alasannya: baik `context.Canceled` ketika dibatalkan secara eksplisit, atau `context.DeadlineExceeded` ketika timeout atau deadline terlewati. ## Membuat Context dengan Background dan TODO Dua fungsi membuat root context: `context.Background()` dan `context.TODO()`. Keduanya mengembalikan context non-nil, kosong yang tidak pernah dibatalkan. ```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()` berfungsi sebagai parent untuk incoming request, fungsi main, dan kode inisialisasi. `TODO()` bertindak sebagai placeholder selama refactoring ketika sumber context yang benar belum ditentukan. Tool analisis statis dapat menandai penggunaan `TODO()` untuk ditinjau lebih lanjut. ## Pembatalan dengan WithCancel dan WithCancelCause Pembatalan manual memungkinkan goroutine parent untuk memberi sinyal kepada children bahwa pekerjaan harus dihentikan. Pola ini muncul dalam worker pool, background task, dan implementasi graceful shutdown. ```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`, yang diperkenalkan di Go 1.20, menyediakan informasi error yang lebih kaya dibandingkan `WithCancel` biasa. Fungsi `context.Cause()` mengambil error yang diteruskan ke fungsi cancel, membuat debugging lebih mudah dalam sistem yang kompleks. ## Timeout dan Deadline untuk Penanganan Request HTTP handler, panggilan database, dan request API eksternal harus selalu memiliki timeout. Package context menawarkan `WithTimeout` dan `WithDeadline` untuk tujuan ini. Keduanya membuat context yang secara otomatis dibatalkan ketika waktu berakhir. ```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) } ``` Pola `defer cancel()` memastikan resource dilepaskan bahkan ketika operasi selesai sebelum timeout. Melewatkan pemanggilan ini menyebabkan resource leak: goroutine timer context tetap aktif hingga timeout berakhir. ## Request-Scoped Values dengan WithValue `WithValue` melampirkan data request-scoped ke context. Kasus penggunaan umum termasuk request ID, token autentikasi, dan tracing span. [Dokumentasi resmi](https://pkg.go.dev/context#WithValue) menekankan penggunaan tipe kustom sebagai key untuk menghindari collision. ```go // context_values.go package main import ( "context" "fmt" "log" "net/http" "time" ) // 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()) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", handler) http.ListenAndServe(":8080", requestIDMiddleware(mux)) } ``` Context value membentuk rantai immutable: setiap pemanggilan `WithValue` membuat context baru yang membungkus parent. Pencarian menelusuri rantai ini, membuat value yang bersarang dalam lebih lambat untuk diakses. Simpan hanya data request-scoped, bukan konfigurasi aplikasi atau parameter opsional. ## Utilitas WithoutCancel dan AfterFunc Go 1.21 menambahkan `WithoutCancel` untuk operasi yang harus selesai terlepas dari pembatalan parent, seperti cleanup task. `AfterFunc` menjadwalkan callback ketika pembatalan context terjadi. ```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` mempertahankan value dari context parent tetapi mengabaikan sinyal pembatalannya. Pola ini cocok untuk logging, emisi metrik, dan database commit yang harus berhasil terlepas dari hasil request. ## Pertanyaan dan Jawaban Wawancara Mempersiapkan wawancara teknis Go memerlukan pemahaman tentang internal context dan praktik terbaik. Pertanyaan-pertanyaan ini sering muncul dalam wawancara di perusahaan yang menggunakan Go untuk layanan backend. Untuk persiapan wawancara Go lebih lanjut, lihat modul [pertanyaan wawancara Context Package](/technologies/go/interview-questions/context-package). ### Mengapa context harus menjadi parameter pertama? Tim Go menetapkan konvensi ini untuk membuat propagasi context terlihat dan konsisten. Menempatkan context di posisi pertama memberi sinyal kepada pembaca bahwa fungsi tersebut menghormati pembatalan. Library standar mengikuti pola ini: `http.Request.Context()`, `database/sql.QueryContext()`, dan `grpc.UnaryInterceptor` semuanya mengharapkan context sebagai argumen pertama. ### Apa yang terjadi jika context disimpan dalam struct? Menyimpan context melanggar model lifecycle request. Sebuah struct mungkin bertahan lebih lama dari request yang membuatnya, menyebabkan operasi menggunakan sinyal pembatalan yang sudah kadaluarsa atau melewatkan deadline baru. Dokumentasi context secara eksplisit memperingatkan terhadap pola ini. ```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) } ``` ### Bagaimana menangani context dalam goroutine? Goroutine harus memeriksa `ctx.Done()` dalam loop utama atau statement select mereka. Mengabaikan channel done menciptakan kebocoran goroutine: fungsi parent kembali, tetapi goroutine terus mengonsumsi resource. ```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) } } } ``` ### Kapan harus menggunakan context.TODO()? Gunakan `TODO()` selama refactoring inkremental saat menambahkan dukungan context ke kode legacy. Ini menandai lokasi yang membutuhkan propagasi context yang tepat. Kode production pada akhirnya harus mengganti semua pemanggilan `TODO()` dengan context aktual dari request handler atau inisialisasi aplikasi. ## Kesalahan Umum yang Harus Dihindari Penyalahgunaan context menyebabkan bug halus yang muncul saat beban tinggi atau selama urutan shutdown. Mengenali pola-pola ini mencegah insiden production. ```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() {} ``` ## Poin Penting untuk Menggunakan Go Context di Production - Teruskan context sebagai parameter pertama ke fungsi, jangan pernah menyimpannya dalam struct - Selalu defer `cancel()` setelah `WithTimeout`, `WithDeadline`, atau `WithCancel` untuk mencegah resource leak - Gunakan `WithCancelCause` dan `context.Cause()` untuk debugging error yang lebih baik dalam rantai pembatalan yang kompleks - Periksa `<-ctx.Done()` dalam loop goroutine untuk mengaktifkan graceful shutdown dan mencegah kebocoran - Gunakan tipe key kustom untuk `WithValue` untuk menghindari collision antar package - Terapkan `WithoutCancel` untuk operasi cleanup yang harus selesai terlepas dari pembatalan parent - Lebih pilih `context.Background()` untuk inisialisasi aplikasi dan `context.TODO()` hanya selama refactoring --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/go/go-context-package-2026