Mastering Go Error Handling: From Basics to Advanced Patterns

This article explains why error handling is crucial in Go, demonstrates the language's explicit error model, walks through basic error representation, return and checking, introduces custom error types, error wrapping, error chains, best‑practice guidelines, and centralised handling patterns with concrete code examples.

Go Development Architecture Practice
Go Development Architecture Practice
Go Development Architecture Practice
Mastering Go Error Handling: From Basics to Advanced Patterns

1. Importance of Error Handling

In software development, proper error handling improves reliability, maintainability, reduces system failures, and enhances user experience.

2. Basic Error Handling

2.1 Error Representation

Go represents errors with the error interface:

type error interface {
    Error() string
}

Any type that implements Error() can be returned as an error.

2.2 Returning Errors

Functions typically return an error as an additional return value:

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

2.3 Checking Errors

Callers must inspect the returned error before using other results:

result, err := divide(10, 0)
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Result:", result)

3. Advanced Error Handling

3.1 Custom Error Types

For complex applications, define custom error structs to carry extra context:

type AppError struct {
    Code    int
    Message string
}

func (e *AppError) Error() string {
    return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}

func processRequest(id int) error {
    if id <= 0 {
        return &AppError{Code: 400, Message: "Invalid ID"}
    }
    // process request …
    return nil
}

func main() {
    err := processRequest(-1)
    if err != nil {
        if appErr, ok := err.(*AppError); ok {
            fmt.Printf("Application error: %s
", appErr)
            fmt.Printf("Error code: %d
", appErr.Code)
        } else {
            fmt.Printf("Generic error: %s
", err)
        }
    }
}

3.2 Error Wrapping (Go 1.13)

Go 1.13 introduced error wrapping with %w to add context while preserving the original error:

func readFile(filename string) error {
    err := openFile(filename)
    if err != nil {
        return fmt.Errorf("failed to read file %s: %w", filename, err)
    }
    return nil
}

func openFile(filename string) error {
    return errors.New("file not found")
}

func main() {
    err := readFile("nonexistent.txt")
    if err != nil {
        fmt.Println("Error:", err)
        var fileErr error = errors.New("file not found")
        if errors.Is(err, fileErr) {
            fmt.Println("This is a file not found error")
        }
    }
}

3.3 Error Chains

Wrapping creates an error chain that can be inspected with errors.Is or errors.As:

func process() error {
    err := readConfig()
    if err != nil {
        return fmt.Errorf("process failed: %w", err)
    }
    return nil
}

func readConfig() error {
    err := readFile("config.json")
    if err != nil {
        return fmt.Errorf("read config failed: %w", err)
    }
    return nil
}

func readFile(filename string) error {
    return errors.New("file not found")
}

func main() {
    err := process()
    if err != nil {
        fmt.Println("Error:", err)
        var fileErr error = errors.New("file not found")
        if errors.Is(err, fileErr) {
            fmt.Println("Root cause: file not found")
        }
    }
}

4. Error‑Handling Best Practices

4.1 Never Ignore Errors

// Bad practice
result, _ := divide(10, 0) // ignore error

// Good practice
result, err := divide(10, 0)
if err != nil {
    // handle error
    return
}

4.2 Return Early on Errors

// Bad practice – deep nesting
func process() error {
    var result int
    var err error
    result, err = step1()
    if err == nil {
        result, err = step2(result)
        if err == nil {
            result, err = step3(result)
            if err == nil {
                // handle result
            }
        }
    }
    return err
}

// Good practice – early returns
func process() error {
    result, err := step1()
    if err != nil {
        return err
    }
    result, err = step2(result)
    if err != nil {
        return err
    }
    result, err = step3(result)
    if err != nil {
        return err
    }
    // handle result
    return nil
}

4.3 Provide Meaningful Error Messages

// Bad practice
if err != nil {
    return errors.New("error")
}

// Good practice
if err != nil {
    return fmt.Errorf("failed to process user %d: %w", userID, err)
}

4.4 Use Wrapping to Add Context

func readFile(filename string) error {
    data, err := ioutil.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("failed to read file %s: %w", filename, err)
    }
    // process data …
    return nil
}

4.5 Distinguish Recoverable vs. Irrecoverable Errors

// Irrecoverable error – abort program
config, err := loadConfig()
if err != nil {
    log.Fatalf("Failed to load config: %v", err)
}

// Recoverable error – continue with fallback
user, err := getUser(123)
if err != nil {
    log.Printf("Failed to get user: %v", err)
    // use default value or continue
}

5. Error‑Handling Libraries

5.1 pkg/errors

Provides stack traces and error wrapping:

import (
    "fmt"
    "github.com/pkg/errors"
)

func readFile(filename string) error {
    return errors.New("file not found")
}

func process() error {
    err := readFile("config.json")
    if err != nil {
        return errors.Wrap(err, "process failed")
    }
    return nil
}

func main() {
    err := process()
    if err != nil {
        fmt.Println("Error:", err)
        fmt.Println("Stack trace:", errors.WithStack(err))
    }
}

5.2 xerrors (Go 1.13)

Another wrapper offering %w semantics:

import (
    "fmt"
    "golang.org/x/xerrors"
)

func readFile(filename string) error {
    return xerrors.New("file not found")
}

func process() error {
    err := readFile("config.json")
    if err != nil {
        return xerrors.Errorf("process failed: %w", err)
    }
    return nil
}

func main() {
    err := process()
    if err != nil {
        fmt.Println("Error:", err)
        fmt.Println("Root cause:", xerrors.Unwrap(err))
    }
}

6. Error‑Handling Patterns

6.1 Centralised Error Handling

For large applications, a single handler can translate errors to HTTP responses:

func handleError(w http.ResponseWriter, err error) {
    switch e := err.(type) {
    case *AppError:
        http.Error(w, e.Message, e.Code)
    default:
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    err := processRequest(r)
    if err != nil {
        handleError(w, err)
        return
    }
    w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, "Success")
}

6.2 Error Logging

Log errors to aid debugging while keeping the program flow clear:

func process() error {
    // business logic …
    return errors.New("process failed")
}

func main() {
    err := process()
    if err != nil {
        log.Printf("Error: %v", err)
        // handle error as needed
    }
}

7. Conclusion

Go's explicit error handling differs from exception‑based languages but offers clear, controllable error management. By mastering representation, return conventions, custom types, wrapping, and best‑practice patterns—including centralised handling and logging—developers can write reliable, maintainable Go programs tailored to their specific scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Gobest practicesError handlingerror wrappingpkg/errorscustom errorsxerrors
Go Development Architecture Practice
Written by

Go Development Architecture Practice

Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.