# Go Error Handling di 2026: Pola, Wrapping, dan Pertanyaan Wawancara Teknis > Panduan lengkap tentang error handling di Go: sentinel error, custom error type, errors.Is, errors.As, error wrapping dengan fmt.Errorf %w, serta pertanyaan wawancara teknis yang sering muncul. - Published: 2026-06-15 - Updated: 2026-06-15 - Author: SharpSkill - Tags: go, error-handling, best-practices, interview - Reading time: 8 min --- Penanganan error di Go mengambil pendekatan yang berbeda secara fundamental dari mayoritas bahasa pemrograman modern. Alih-alih menggunakan mekanisme exception dan blok try/catch, Go memperlakukan error sebagai nilai yang dikembalikan secara eksplisit dari setiap fungsi. Keputusan desain ini memaksa setiap jalur kegagalan terlihat jelas di kode sumber, menjadikan alur error dapat dibaca dan diuji secara langsung. > **Error Sebagai Nilai, Bukan Exception** > > Go tidak memiliki mekanisme exception. Setiap fungsi yang mungkin gagal mengembalikan error sebagai return value terakhir. Pola ini menjadikan biaya dari mengabaikan error terlihat langsung pada level kode sumber — dan membuat alur error dapat diuji secara trivial. ## Interface Error dan Mengapa Hal Ini Penting Seluruh sistem error di Go berdiri di atas satu interface tunggal yang didefinisikan dalam [standard library](https://pkg.go.dev/builtin#error): ```go // builtin.go type error interface { Error() string } ``` Tipe apa pun yang mengimplementasikan method `Error() string` memenuhi interface ini. Kesederhanaan ini mendorong komposabilitas: error dapat berupa struct, wrapper, atau bentuk apa pun selama menghasilkan representasi string. Berikut contoh custom error type yang mendemonstrasikan pola dasar: ```go // apperror.go type NotFoundError struct { Resource string ID string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID) } ``` `NotFoundError` membawa data terstruktur. Pemanggil dapat mengekstrak nama resource atau ID secara programatik tanpa perlu melakukan parsing string — keuntungan kritis ketika membangun HTTP API atau CLI tool yang memetakan error ke response spesifik. ## Sentinel Error untuk Kondisi yang Sudah Diketahui Sentinel error adalah variabel tingkat package yang merepresentasikan kondisi kegagalan spesifik yang sudah umum diketahui. Standard library menggunakan pola ini secara luas: `io.EOF`, `sql.ErrNoRows`, `os.ErrNotExist`. ```go // errors.go var ( ErrNotFound = errors.New("record not found") ErrUnauthorized = errors.New("unauthorized access") ErrConflict = errors.New("resource conflict") ) ``` Sentinel error paling tepat digunakan ketika pemanggil hanya perlu mengetahui *apa* yang gagal, bukan *mengapa* secara detail. Sentinel error menandakan suatu kondisi tanpa membawa konteks tambahan. Konvensi Go adalah memberi awalan `Err` dan menjaga pesan huruf kecil tanpa tanda baca, mengikuti [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments#error-strings). > **Ketika Sentinel Menjadi Masalah** > > Mengekspor sentinel error menciptakan kontrak API publik. Package downstream akan mencocokkannya dengan `errors.Is`, sehingga mengganti nama atau menghapus sentinel error merupakan breaking change. Gunakan secara hemat — hanya untuk kondisi yang benar-benar perlu dijadikan titik percabangan oleh pemanggil. ## Error Wrapping dengan fmt.Errorf dan Verb %w Error wrapping, yang diperkenalkan di Go 1.13, menambahkan konteks ke sebuah error sambil mempertahankan error asli dalam sebuah rantai. Verb `%w` di `fmt.Errorf` membuat rantai ini: ```go // repository.go func (r *UserRepo) FindByID(ctx context.Context, id string) (*User, error) { user, err := r.db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", id) if err != nil { return nil, fmt.Errorf("UserRepo.FindByID(%s): %w", id, err) } return user, nil } ``` Error yang di-wrap mempertahankan seluruh rantai. Pemanggil yang berada tiga lapisan di atas masih dapat mencocokkan sentinel asli atau mengekstrak tipe aslinya. Setiap lapisan menambahkan konteks tentang *di mana* error terjadi tanpa mengaburkan *apa* yang terjadi. Perbedaan kritis yang perlu dipahami: `%w` melakukan wrap (mempertahankan rantai), sementara `%v` memformat error sebagai string dan memutus rantai. Gunakan `%v` secara sengaja ketika error asli tidak boleh bocor melewati batas abstraksi — misalnya, ketika repository membungkus error driver database yang tidak seharusnya diperiksa oleh kode di lapisan service. ## Inspeksi Error dengan errors.Is dan errors.As Package `errors` menyediakan dua fungsi untuk menginspeksi rantai error yang sudah di-wrap, menggantikan perbandingan langsung dan type assertion. `errors.Is` menelusuri rantai untuk mencari nilai error tertentu: ```go // handler.go func handleGetUser(w http.ResponseWriter, r *http.Request) { user, err := userService.GetByID(r.Context(), chi.URLParam(r, "id")) if errors.Is(err, ErrNotFound) { http.Error(w, "User not found", http.StatusNotFound) return } if err != nil { http.Error(w, "Internal error", http.StatusInternalServerError) return } json.NewEncoder(w).Encode(user) } ``` `errors.As` mengekstrak tipe error tertentu dari rantai: ```go // middleware.go func errorMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { http.Error(w, "Internal error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } func mapErrorToHTTP(err error) int { var notFound *NotFoundError if errors.As(err, ¬Found) { return http.StatusNotFound } var validationErr *ValidationError if errors.As(err, &validationErr) { return http.StatusBadRequest } return http.StatusInternalServerError } ``` Keunggulan utama dibandingkan type assertion: kedua fungsi ini menelusuri seluruh rantai yang di-wrap. Sebuah `NotFoundError` yang di-wrap tiga kali dengan `fmt.Errorf` dan `%w` tetap dapat dicocokkan. ## Penanganan Error Terstruktur pada Aplikasi Berlapis Aplikasi Go untuk produksi biasanya mengorganisasi error dalam tiga lapisan: domain error mendefinisikan kondisi bisnis, service error menambahkan konteks operasional, dan [handler atau transport error](https://www.datadoghq.com/blog/go-error-handling/) memetakannya ke response eksternal. ```go // domain/errors.go type DomainError struct { Code string Message string Err error } func (e *DomainError) Error() string { if e.Err != nil { return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } func (e *DomainError) Unwrap() error { return e.Err } ``` Method `Unwrap` adalah kunci yang membuat `errors.Is` dan `errors.As` dapat bekerja menelusuri rantai. Setiap custom error type yang membungkus error lain harus mengimplementasikan method ini. Lapisan service membungkus domain error dengan konteks operasional: ```go // service/user.go func (s *UserService) Deactivate(ctx context.Context, userID string) error { user, err := s.repo.FindByID(ctx, userID) if err != nil { return fmt.Errorf("deactivating user %s: %w", userID, err) } if user.Status == StatusInactive { return &DomainError{ Code: "ALREADY_INACTIVE", Message: fmt.Sprintf("user %s is already inactive", userID), } } return s.repo.UpdateStatus(ctx, userID, StatusInactive) } ``` ## Pertanyaan Wawancara Seputar Error Handling di Go Wawancara teknis untuk posisi Go secara rutin menguji pemahaman tentang error handling. Pertanyaan-pertanyaan berikut sering muncul dalam sesi screening dan dibahas secara mendalam di [pertanyaan wawancara error handling Go](/technologies/go/interview-questions/error-handling). **Mengapa Go menggunakan error value alih-alih exception?** Exception menciptakan alur kontrol yang tidak terlihat. Sebuah fungsi yang melempar exception mentransfer kontrol ke catch site yang tidak diketahui, berpotensi melewati banyak stack frame. Error value di Go membuat setiap jalur kegagalan eksplisit dalam signature fungsi. Pemanggil memutuskan secara langsung bagaimana menangani error — retry, wrap, log, atau propagate. Pendekatan ini menghilangkan masalah "surprise throw" dan membuat alur error dapat dibaca tanpa bantuan tooling khusus. **Apa perbedaan antara `%w` dan `%v` di `fmt.Errorf`?** `%w` melakukan wrap terhadap error, mempertahankan rantai untuk `errors.Is` dan `errors.As`. `%v` memformat error sebagai string, menghasilkan error baru tanpa tautan rantai kembali ke error asli. Gunakan `%w` ketika pemanggil perlu dapat menginspeksi penyebab; gunakan `%v` ketika penyebab merupakan detail implementasi yang tidak boleh bocor melewati batas abstraksi. **Kapan sebuah fungsi sebaiknya mengembalikan error vs panic?** Panic diperuntukkan untuk situasi yang benar-benar tidak dapat dipulihkan: kesalahan pemrograman seperti index out of bounds, nil pointer dereference, atau invariant yang dilanggar yang mengindikasikan bug. Kegagalan yang dapat dipulihkan — timeout jaringan, record tidak ditemukan, input tidak valid — mengembalikan error. Aturan praktis: jika kondisi tersebut dapat terjadi selama operasi normal, kembalikan error. Jika kondisi tersebut berarti program memiliki bug, gunakan panic. **Bagaimana method `Unwrap` memengaruhi inspeksi rantai error?** Ketika custom error type mengimplementasikan `Unwrap() error`, fungsi `errors.Is` dan `errors.As` mengikuti method tersebut untuk menelusuri rantai. Tanpa `Unwrap`, rantai berhenti pada error tersebut. Sejak Go 1.20, error juga dapat mengimplementasikan `Unwrap() []error` untuk mengembalikan beberapa wrapped error, memungkinkan rantai error berbentuk tree untuk kasus seperti agregasi validation error. ## Pola Error Handling yang Harus Dihindari Beberapa anti-pattern sering muncul dalam codebase Go dan dalam [sesi code review](/blog/go/go-design-patterns-essential-patterns-interview): **Mengabaikan error secara diam-diam.** Blank identifier membuat hal ini mudah secara sintaksis tetapi berbahaya secara semantik: ```go // anti-pattern: silent error swallowing result, _ := riskyOperation() ``` Setiap error yang diabaikan harus merupakan pilihan yang disengaja dan terdokumentasi — bukan jalan pintas. **Logging sekaligus mengembalikan error.** Pola ini menghasilkan entri log duplikat dan membingungkan pemanggil yang juga menangani error: ```go // anti-pattern: double handling if err != nil { log.Printf("operation failed: %v", err) return err } ``` Solusinya: tangani error (log, kembalikan nilai default, retry) *atau* propagasikan. Jangan keduanya. **Pencocokan string untuk klasifikasi error.** Memeriksa output `err.Error()` dengan `strings.Contains` bersifat rapuh. Pesan error bukan kontrak API — pesan dapat berubah antar versi library. Gunakan `errors.Is` untuk sentinel value dan `errors.As` untuk tipe. **Membungkus error yang melewati batas goroutine tanpa sinkronisasi.** Ketika beberapa [goroutine](/blog/go/go-concurrency-goroutines-channels) menghasilkan error secara bersamaan, gunakan `errgroup.Group` dari package `golang.org/x/sync` atau mekanisme sinkronisasi serupa. Menambahkan langsung ke shared slice tanpa mutex menciptakan data race. ## Error Handling dengan errgroup untuk Operasi Konkuren Package `errgroup` dari [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync/errgroup) menyediakan pola yang bersih untuk mengumpulkan error dari goroutine konkuren: ```go // batch.go func (s *OrderService) ProcessBatch(ctx context.Context, orderIDs []string) error { g, ctx := errgroup.WithContext(ctx) g.SetLimit(10) for _, id := range orderIDs { g.Go(func() error { if err := s.processOrder(ctx, id); err != nil { return fmt.Errorf("processing order %s: %w", id, err) } return nil }) } return g.Wait() } ``` `errgroup.WithContext` membatalkan context turunan ketika salah satu goroutine mengembalikan error, memberi sinyal kepada goroutine lainnya untuk berhenti. Pendekatan ini menghindari pekerjaan yang sia-sia dan menyediakan satu error yang sudah di-wrap kepada pemanggil. > **Go 1.24 dan Error Handling** > > Go 1.24 (Februari 2025) tidak mengubah package errors. API error handling telah stabil sejak Go 1.13, dengan penambahan multi-error unwrapping melalui `Unwrap() []error` di Go 1.20. Proposal terkini untuk perbaikan sintaks error handling masih dalam tahap diskusi di Go issue tracker. ## Kesimpulan - Perlakukan error sebagai nilai: kembalikan secara eksplisit dan tangani di setiap call site alih-alih mengandalkan mekanisme exception tersembunyi - Gunakan sentinel error (`var ErrX = errors.New(...)`) hanya untuk kondisi yang perlu dijadikan titik percabangan oleh pemanggil, dan perlakukan sebagai API publik - Bungkus error dengan `fmt.Errorf` dan `%w` untuk menambahkan konteks sambil mempertahankan rantai; gunakan `%v` untuk secara sengaja memutus rantai pada batas abstraksi - Lebih utamakan `errors.Is` daripada `==` dan `errors.As` daripada type assertion — keduanya menelusuri seluruh rantai yang di-wrap - Implementasikan `Unwrap() error` pada custom error type agar inspeksi rantai berfungsi dengan benar - Tangani atau propagasikan — jangan keduanya. Melakukan log terhadap error lalu mengembalikannya menghasilkan noise duplikat - Gunakan `errgroup` untuk pengumpulan error konkuren alih-alih sinkronisasi goroutine secara manual - Jaga pesan error tetap huruf kecil tanpa tanda baca, mengikuti konvensi Go --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/go/go-error-handling-patterns-wrapping-best-practices