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.

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.
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 mendefinisikan empat metode yang harus dipenuhi oleh setiap implementasi context. Memahami metode-metode ini membentuk dasar untuk penggunaan context yang efektif.
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.
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.
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.
Siap menguasai wawancara Go Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
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.
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 menekankan penggunaan tipe kustom sebagai key untuk menghindari collision.
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.
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.
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.
// 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.
// 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.
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() {}Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Poin Penting untuk Menggunakan Go Context di Production
- Teruskan context sebagai parameter pertama ke fungsi, jangan pernah menyimpannya dalam struct
- Selalu defer
cancel()setelahWithTimeout,WithDeadline, atauWithCanceluntuk mencegah resource leak - Gunakan
WithCancelCausedancontext.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
WithValueuntuk menghindari collision antar package - Terapkan
WithoutCanceluntuk operasi cleanup yang harus selesai terlepas dari pembatalan parent - Lebih pilih
context.Background()untuk inisialisasi aplikasi dancontext.TODO()hanya selama refactoring
Bisakah kamu menemukan bug di Go?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 29 Agustus 2026
Bagikan
Artikel terkait

Profiling dan Benchmarking Go 2026: pprof, trace, dan Pertanyaan Interview
Panduan lengkap profiling Go dengan pprof dan runtime/trace. Teknik analisis CPU, memori, dan goroutine untuk optimasi performa serta persiapan interview.

Interface Go Tingkat Lanjut di 2026: Komposisi, Type Assertion, dan Pertanyaan Interview
Panduan mendalam tentang interface Go modern: komposisi interface, type assertion yang aman, generic self-referential, dan pertanyaan interview yang sering muncul untuk developer Go.

Go Testing di 2026: Unit Test, Mock, dan Pertanyaan Wawancara Teknis
Kuasai testing Go dengan table-driven test, mock, dan pola yang diharapkan pewawancara. Contoh praktis dengan testify dan gomock.