Error Handling in Go
Error handling is one of the most important concepts in Go. Unlike many languages that use exceptions, Go uses explicit error values. This design forces developers to remember, check, and handle errors properly.
In real production systems, correct error handling is the difference between a stable application and unpredictable failures.
Why Go Uses Explicit Errors
Go avoids hidden control flow caused by exceptions. Instead, errors are returned as values and handled immediately.
This approach makes programs:
- Easier to read
- More predictable
- Safer in production
The error Type
In Go, error is a built-in interface.
type error interface {
Error() string
}
Any type that implements the Error() method is an error.
Returning Errors from Functions
Most Go functions return a value and an error.
func divide(a int, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
If an error occurs, return a meaningful error message.
If everything is fine, return nil as the error.
Handling Errors Properly
Always check the error before using the returned value.
result, err := divide(100, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
Ignoring errors is one of the most common beginner mistakes in Go.
Using the errors.New() Function
The errors package allows you to create simple error messages.
import "errors"
err := errors.New("invalid input")
This is useful for clear and readable error messages.
Formatting Errors with fmt.Errorf()
For dynamic error messages, use fmt.Errorf().
func withdraw(balance int, amount int) error {
if amount > balance {
return fmt.Errorf("insufficient funds: balance=%d, amount=%d", balance, amount)
}
return nil
}
This allows you to include real data inside error messages.
Sentinel Errors
A sentinel error is a predefined error value. It allows comparison using equality.
var ErrNotFound = errors.New("record not found")
Usage:
if err == ErrNotFound {
fmt.Println("Handle missing data")
}
Wrapping Errors
Go supports error wrapping using %w.
This preserves the original error context.
func readFile() error {
err := os.ErrNotExist
return fmt.Errorf("file read failed: %w", err)
}
Wrapped errors allow layered error handling in large systems.
Checking Wrapped Errors
Use errors.Is() to check wrapped errors.
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
Custom Error Types
For complex applications, define custom error types.
type ValidationError struct {
Field string
Msg string
}
func (e ValidationError) Error() string {
return e.Field + ": " + e.Msg
}
Usage:
return ValidationError{
Field: "email",
Msg: "invalid format",
}
Real-World Example
Bank transaction validation:
func transfer(balance int, amount int) error {
if amount <= 0 {
return errors.New("amount must be positive")
}
if amount > balance {
return errors.New("insufficient balance")
}
return nil
}
Common Error Handling Mistakes
- Ignoring returned errors
- Using panic for normal flow
- Returning vague error messages
- Overusing global errors
Best Practices
- Check errors immediately
- Return meaningful messages
- Wrap errors when adding context
- Avoid panic in application logic
What’s Next?
In the next lesson, you will learn about Defer, Panic, and Recover and how Go handles unexpected runtime situations safely.