Skip to main content
Go Error Handling

Error Handling in Go

Go takes a unique approach to error handling. Instead of try/catch exceptions found in languages like Java or Python, Go treats errors as values. This forces developers to handle errors explicitly where they occur, leading to more robust and predictable software.

The error Interface

In Go, an error is anything that implements the built-in error interface:
type error interface {
    Error() string
}

Basic Error Handling

Functions often return an error as the last return value.
file, err := os.Open("filename.txt")
if err != nil {
    log.Fatal(err)
}
// use file

Creating Errors

You can create simple errors using errors.New or fmt.Errorf.
import "errors"

func validate(input int) error {
    if input < 0 {
        return errors.New("input cannot be negative")
    }
    return nil
}

Custom Error Types

Since error is an interface, you can create custom structs that implement it to provide more context.
type NetworkError struct {
    Code    int
    Message string
}

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

Wrapping and Unwrapping Errors

Go 1.13 introduced error wrapping, allowing you to add context to an error while preserving the original error. This creates an “error chain” that can be inspected programmatically.

Wrapping

Use the %w verb with fmt.Errorf to wrap an error:
if err != nil {
    return fmt.Errorf("failed to connect to database: %w", err)
}
Why Wrap? Error wrapping provides a “stack trace” of context without losing the original error. Each layer adds information about where and why the error occurred. Go Error Wrapping Example Error Chain:
initialization failed: failed to read config: EOF
                                              ^
                                              Original error (io.EOF)

Unwrapping (errors.Is and errors.As)

When you have a wrapped error, you need special functions to inspect the chain:
  • errors.Is(err, target): Checks if any error in the chain matches the target error.
  • errors.As(err, &target): Finds the first error in the chain that matches the target type and assigns it.
import (
    "errors"
    "io"
)

// Check for specific error
if errors.Is(err, io.EOF) {
    // Handle end of file
}

// Extract custom error type
var netErr *NetworkError
if errors.As(err, &netErr) {
    fmt.Println("Network error code:", netErr.Code)
    // Can now access all fields of NetworkError
}
Why Not Just Compare? Simple comparison (err == io.EOF) only works for the outermost error. errors.Is traverses the entire chain.
err := fmt.Errorf("wrapped: %w", io.EOF)
fmt.Println(err == io.EOF)      // false (wrapped)
fmt.Println(errors.Is(err, io.EOF)) // true (unwraps and checks)

Panic and Recover

Go has a mechanism for exceptional conditions called panic. It should be used sparingly, mostly for unrecoverable errors (like nil pointer dereferences or invariant violations).

Panic

panic stops the ordinary flow of control and begins panicking.
panic("something went terribly wrong")

Recover

recover is a built-in function that regains control of a panicking goroutine. It is only useful inside a deferred function.
func safeFunction() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    
    // Code that might panic
    panic("oops")
}

Best Practices

  1. Don’t Ignore Errors: Always check the err return value. Use _ only if you are absolutely sure the error is irrelevant.
  2. Add Context: Wrap errors to provide “stack trace”-like context (e.g., “loading config: parsing json: syntax error”).
  3. Avoid Panic: Use errors for normal control flow. Reserve panic for truly exceptional, unrecoverable states.

Summary

  • Errors are values in Go, implementing the error interface.
  • Handle errors explicitly using if err != nil.
  • Use fmt.Errorf with %w to wrap errors with context.
  • Use errors.Is and errors.As to inspect wrapped errors.
  • panic and recover are for exceptional circumstances, not normal flow control.