Golang / GoLang Production Patterns and Web Standards Interview Questions
What are the best practices for defining custom error types in Go?
Custom error types are used when callers need to inspect error details beyond a simple message. The key decision is whether to use a pointer receiver (most common for structs) or a value receiver, and whether to implement the optional Unwrap() method to participate in the error chain.
// Pattern 1: simple sentinel error (no extra data) var ErrRateLimited = errors.New("rate limited") // Pattern 2: structured error with context type HTTPError struct { Code int Message string Cause error // optional wrapped cause } func (e *HTTPError) Error() string { if e.Cause != nil { return fmt.Sprintf("HTTP %d: %s: %v", e.Code, e.Message, e.Cause) } return fmt.Sprintf("HTTP %d: %s", e.Code, e.Message) } // Implement Unwrap() to participate in errors.Is / errors.As chain func (e *HTTPError) Unwrap() error { return e.Cause } // Constructor Γ’ΒΒ always return concrete type as error interface func newHTTPError(code int, msg string, cause error) error { return &HTTPError{Code: code, Message: msg, Cause: cause} } // Usage func callAPI(url string) error { resp, err := http.Get(url) if err != nil { return newHTTPError(0, "request failed", err) } if resp.StatusCode == http.StatusTooManyRequests { return newHTTPError(429, "rate limited", ErrRateLimited) } return nil } // Caller err := callAPI("https://api.example.com/data") var httpErr *HTTPError if errors.As(err, &httpErr) { log.Printf("HTTP error %d: %s", httpErr.Code, httpErr.Message) } if errors.Is(err, ErrRateLimited) { time.Sleep(time.Second) // back off and retry } // ANTI-PATTERN: typed nil trap // func getUser() error { // var e *HTTPError // return e // NOT nil interface! {type=*HTTPError, data=nil} // } // Always: return nil (bare), not a typed nil pointer
More Related questions...