← Back to Blog

Go Error Handling: errors.Is, errors.As, and Wrapping Best Practices

May 27, 2026 3 min read By CodeTidy Team

The Error Handling Conundrum

Have you ever found yourself drowning in a sea of errors, wondering which one to tackle first? Or perhaps you've spent hours debugging a cryptic error message, only to discover it was a simple mistake. We've all been there. Effective error handling is crucial in Go programming, and yet, it's often an afterthought.

Table of Contents

  • Error Handling Basics
  • The Power of errors.Is and errors.As
  • Error Wrapping Best Practices
  • Custom Error Types and Sentinel Errors
  • Putting it all Together with a Real-World Example
  • Key Takeaways
  • FAQ

Error Handling Basics

In Go, errors are values that can be returned from functions to indicate that something went wrong. The error type is an interface that defines a single method, Error() string, which returns a string describing the error. We can create errors using the errors.New function, like this:

err := errors.New("something went wrong")

However, this approach has its limitations. What if we want to provide more context about the error? What if we want to differentiate between different types of errors?

The Power of errors.Is and errors.As

The errors.Is function checks if a given error is equal to a target error. This is useful for checking if an error is a specific type, like io.EOF. The errors.As function, on the other hand, checks if an error can be asserted to a specific type.

var err error
err = io.EOF

if errors.Is(err, io.EOF) {
    fmt.Println("end of file")
}

var target *MyError
if errors.As(err, &target) {
    fmt.Println("my error:", target.Msg)
}

We recommend using errors.Is and errors.As instead of type assertions or direct comparisons, as they provide more flexibility and expressiveness.

Error Wrapping Best Practices

Error wrapping is a technique where we wrap an existing error with additional context. This is useful for providing more information about the error without losing the original error message. We can use the %w directive with fmt.Errorf to wrap an error:

err := errors.New("original error")
wrappedErr := fmt.Errorf("context: %w", err)

When wrapping errors, we should follow these best practices:

  • Use the %w directive to wrap errors, not fmt.Sprintf.
  • Keep the original error message intact.
  • Provide additional context that is useful for debugging.

Custom Error Types and Sentinel Errors

Custom error types are useful for defining specific error types that can be used throughout our codebase. We can define a custom error type using a struct that implements the error interface:

type MyError struct {
    Msg string
}

func (e *MyError) Error() string {
    return e.Msg
}

Sentinel errors, on the other hand, are errors that have a specific meaning throughout our codebase. They can be used to indicate a specific condition, like io.EOF.

Putting it all Together with a Real-World Example

Let's say we're building a file reader that needs to handle errors when reading a file. We can use a combination of error handling techniques to provide a robust and informative error handling system:

func readFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("failed to open file: %w", err)
    }
    defer file.Close()

    // read file contents
    contents, err := ioutil.ReadAll(file)
    if err != nil {
        return fmt.Errorf("failed to read file: %w", err)
    }

    // process file contents
    if err := processContents(contents); err != nil {
        return fmt.Errorf("failed to process file: %w", err)
    }

    return nil
}

func processContents(contents []byte) error {
    // simulate an error
    return &MyError{"invalid contents"}
}

func main() {
    err := readFile("example.txt")
    if err != nil {
        if errors.Is(err, &MyError{}) {
            fmt.Println("invalid contents")
        } else {
            fmt.Println("error:", err)
        }
    }
}

In this example, we use a combination of error wrapping, custom error types, and sentinel errors to provide a robust error handling system.

Key Takeaways

  • Use errors.Is and errors.As to check if an error is a specific type or can be asserted to a specific type.
  • Use error wrapping to provide additional context about an error.
  • Define custom error types to provide specific error types throughout your codebase.
  • Use sentinel errors to indicate specific conditions throughout your codebase.

FAQ

Q: What is the difference between errors.New and fmt.Errorf?

A: errors.New creates a new error with a given message, while fmt.Errorf creates a new error with a formatted message.

Q: How do I use errors.Is and errors.As?

A: Use errors.Is to check if an error is equal to a target error, and use errors.As to check if an error can be asserted to a specific type.

Q: What is error wrapping?

A: Error wrapping is a technique where we wrap an existing error with additional context.

AI agent tools available. The CodeTidy MCP Server gives Claude, Cursor, and other AI agents access to 60+ developer tools. One command: npx @codetidy/mcp