← Home
gobackend

Go Error Handling Patterns


Why Error Handling Matters

Go’s explicit error handling is one of its defining features. Unlike languages that use exceptions, Go treats errors as values. This makes error flows visible and forces developers to handle failures at every step.

f, err := os.Open("config.yaml")
if err != nil {
    return fmt.Errorf("open config: %w", err)
}
defer f.Close()

Sentinel Errors

Sentinel errors are package-level variables that represent specific, well-known error conditions.

var ErrNotFound = errors.New("not found")

func GetUser(id string) (*User, error) {
    u, ok := store[id]
    if !ok {
        return nil, ErrNotFound
    }
    return u, nil
}

Callers can then use errors.Is to check:

if errors.Is(err, ErrNotFound) {
    http.Error(w, "user not found", 404)
}

Error Wrapping

Since Go 1.13, you can wrap errors to add context while preserving the original:

func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load config %s: %w", path, err)
    }
    // ...
}

Custom Error Types

For richer error information, define a struct that implements the error interface:

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

Summary

PatternUse When
Sentinel errorsWell-known, specific conditions
fmt.Errorf with %wAdding context to existing errors
Custom typesNeed structured error data

Good error handling makes debugging easier and your APIs more predictable.