Go Lesson 27 – Error Handling | Dataplexa

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.