codingstuff.io
ExploreTutorialsProblemsCS Subjects
Get Started
ExploreTutorialsProblemsCS Subjects
Get Started
codingstuff.io

Master the art of building software through interactive tutorials, real-world problems, and guided projects.

Pune, Maharashtra, India

codingstuffmail@gmail.com

Product

  • Explore
  • Tutorials
  • Problems
  • CS Subjects

Company

  • About
  • Contact
  • Privacy Policy
  • Terms & Conditions
  • Sitemap

© 2026 codingstuff.io. All rights reserved.

Built with ❤️ for developers everywhere

/
/
All Tutorials
🐹

Go (Golang)

22 / 72 topics
22Error Handling23Testing in Go24Benchmarking25Profiling26Code Organization and Packages
Tutorials/Go (Golang)/Error Handling
🐹Go (Golang)

Error Handling

Updated 2026-04-20
3 min read

Introduction

Error handling is a critical aspect of software development, ensuring that your application can gracefully handle unexpected situations and provide meaningful feedback to users or operators. In Go (Golang), error handling is idiomatic and plays a central role in the language's design philosophy. This tutorial will explore best practices for error handling in Go, covering topics such as error types, error propagation, and custom error handling.

Understanding Errors in Go

In Go, an error is represented by the built-in error interface:

type error interface {
    Error() string
}

This means any type that implements the Error() method can be used as an error. Commonly, errors are created using the errors.New() function or formatted with fmt.Errorf().

Creating Errors

Here’s how you can create simple and formatted errors:

package main

import (
    "errors"
    "fmt"
)

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

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

Custom Error Types

For more complex error handling, you can define custom error types:

package main

import (
    "fmt"
)

type DivisionError struct {
    Message string
    Code    int
}

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

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, &DivisionError{
            Message: "division by zero",
            Code:    1,
        }
    }
    return a / b, nil
}

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

Error Propagation

In Go, errors are typically propagated up the call stack. This means that functions should return errors when they encounter issues, and calling functions should handle these errors appropriately.

Returning Errors

Here’s an example of returning errors from a function:

package main

import (
    "fmt"
)

func readConfig() error {
    // Simulate reading a configuration file
    err := fmt.Errorf("failed to read config")
    return err
}

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

func main() {
    if err := initApp(); err != nil {
        fmt.Println("Application initialization failed:", err)
    } else {
        fmt.Println("Application initialized successfully")
    }
}

Wrapping Errors

Go 1.13 introduced the fmt.Errorf function with the %w verb, which allows you to wrap errors while preserving their type and stack trace:

package main

import (
    "fmt"
)

func readConfig() error {
    // Simulate reading a configuration file
    err := fmt.Errorf("failed to read config")
    return fmt.Errorf("readConfig: %w", err)
}

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

func main() {
    if err := initApp(); err != nil {
        fmt.Println("Application initialization failed:", err)
    } else {
        fmt.Println("Application initialized successfully")
    }
}

Best Practices for Error Handling

1. Always Check Errors

Ensure that every function call that returns an error is checked:

file, err := os.Open(filename)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

2. Use Custom Error Types Sparingly

Custom error types are useful for more complex applications where you need to handle specific errors differently. However, overusing them can lead to code bloat and complexity.

3. Propagate Errors Gracefully

When propagating errors, wrap them with context using fmt.Errorf to provide more information about the error's origin:

if err := someFunction(); err != nil {
    return fmt.Errorf("someWrapper: %w", err)
}

4. Use Sentinel Values Cautiously

Sentinel values are specific error instances that you compare against, like io.EOF. While they can be useful for simple checks, they should be used sparingly as they can lead to fragile code.

5. Handle Errors at the Right Level

Handle errors at the level where you have enough context to make a decision about how to proceed. Avoid catching and ignoring errors unless you are sure that it is safe to do so.

6. Log Errors with Context

When logging errors, include as much context as possible to aid in debugging:

if err != nil {
    log.Printf("Failed to process request: %v", err)
}

Conclusion

Effective error handling is essential for building robust and maintainable applications in Go. By understanding how to create, propagate, and handle errors, you can ensure that your application can gracefully manage unexpected situations and provide meaningful feedback. Following best practices such as always checking errors, using custom error types judiciously, and propagating errors with context will help you write more reliable and efficient code.

Remember, error handling is not just about catching errors; it's also about understanding the flow of your program and making informed decisions when things go wrong.


PreviousSync PackageNext Testing in Go

Recommended Gear

Sync PackageTesting in Go