GO

Go Fundamentals

18 lessons

Progress0%
1. Introduction to Go
1What is Go?
2. Variables and Data Types
1Data Types in Go
3. Control Flow
If, For, and SwitchDefer, Panic, Recover
4. Functions
Function BasicsError Handling
5. Structs and Methods
StructsMethods and Interfaces
6. Concurrency
Goroutines and ChannelsSelect and Sync
7. Maps & Slices Advanced
Slices Deep DiveMaps Operations & Patterns
8. Interfaces Deep Dive
Interface Composition & anyCommon Interfaces & Patterns
9. Packages & Modules
Package SystemGo Modules & Workspace
10. Testing & Standard Library
Testing in GoStandard Library Essentials
All Tutorials
GoFunctions
Lesson 6 of 18 min
Chapter 4 · Lesson 2

Error Handling

Error Handling in Go

Go handles errors explicitly by returning them as values — there are no exceptions.

The error interface error is a built-in interface with one method:

go
type error interface { Error() string }

Return nil for success, a non-nil error for failure.

Creating errors

  • errors.New("message") — simple error value.
  • fmt.Errorf("context: %w", err) — formats a message and optionally wraps an existing error.

Wrapping errors with %w Wrapping preserves the original error for inspection:

go
err = fmt.Errorf("open config: %w", originalErr)

errors.Is and errors.As

  • errors.Is(err, target) — checks if any error in the chain equals target (useful for sentinel errors).
  • errors.As(err, &target) — unwraps and assigns the first error of target's type.

Key points:

  • Always check errors immediately after the call that produced them.
  • The pattern if err != nil { return …, err } propagates errors up the call stack.
  • Wrapping with %w lets callers inspect the chain without exposing internal details.

Code Examples

Returning and checking errorsgo
package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")

func findUser(id int) (string, error) {
	users := map[int]string{1: "Alice", 2: "Bob"}
	name, ok := users[id]
	if !ok {
		return "", fmt.Errorf("findUser(%d): %w", id, ErrNotFound)
	}
	return name, nil
}

func main() {
	name, err := findUser(1)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Found:", name)
	}

	_, err = findUser(99)
	if err != nil {
		fmt.Println("Error:", err)
		fmt.Println("Is ErrNotFound:", errors.Is(err, ErrNotFound))
	}
}

Sentinel errors like ErrNotFound allow callers to check for specific conditions with errors.Is, even when the error is wrapped.

Custom error types and errors.Asgo
package main

import (
	"errors"
	"fmt"
)

type ValidationError struct {
	Field   string
	Message string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
	if age < 0 {
		return &ValidationError{Field: "age", Message: "must be non-negative"}
	}
	if age > 150 {
		return &ValidationError{Field: "age", Message: "unrealistically large"}
	}
	return nil
}

func main() {
	err := validateAge(-5)
	if err != nil {
		var ve *ValidationError
		if errors.As(err, &ve) {
			fmt.Printf("Field: %s\nMessage: %s\n", ve.Field, ve.Message)
		}
	}
}

errors.As unwraps an error chain looking for a specific type. Custom error types carry structured information beyond a plain string.

Quick Quiz

1. What does `fmt.Errorf("context: %w", err)` do to the original error?

2. What is the zero value of an `error` indicating success?

Was this lesson helpful?

PreviousNext