Menu
Coddy logo textTech

Sentinel Errors

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 55 of 107.

A sentinel error is a predefined, package-level error variable that represents a specific error condition. Unlike creating new errors each time with errors.New, sentinel errors are declared once and reused throughout your code.

You define sentinel errors as package-level variables, typically with names starting with Err:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

Functions return these predefined errors when specific conditions occur:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

The power of sentinel errors is that callers can check for specific error conditions by comparing error values directly:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Go's standard library uses sentinel errors extensively. For example, io.EOF signals the end of input, and sql.ErrNoRows indicates an empty query result. This pattern creates a clear contract between packages and their users about what errors to expect.

However, simple equality checks break when errors are wrapped. In the next lesson, you'll learn how errors.Is() solves this problem by checking the entire error chain.

challenge icon

Challenge

Easy

Let's build an inventory management system that uses sentinel errors to communicate specific failure conditions. You'll define package-level error variables that callers can check to handle different scenarios appropriately.

You'll organize your code across two files:

  • inventory.go: Create an inventory system with predefined sentinel errors for common failure cases.

    Define three sentinel errors at the package level:

    • ErrItemNotFound with message "item not found in inventory"
    • ErrOutOfStock with message "item is out of stock"
    • ErrInsufficientQuantity with message "insufficient quantity available"

    Create an Inventory struct that holds a map of item names to their quantities. Implement these methods:

    • NewInventory() *Inventory - creates an empty inventory
    • AddItem(name string, quantity int) - adds or updates an item's quantity
    • GetQuantity(name string) (int, error) - returns the quantity for an item, or ErrItemNotFound if it doesn't exist
    • Purchase(name string, quantity int) error - attempts to purchase items. Return ErrItemNotFound if the item doesn't exist, ErrOutOfStock if the current quantity is zero, or ErrInsufficientQuantity if the requested amount exceeds available stock. On success, reduce the inventory and return nil.
  • main.go: Read inventory operations from input and demonstrate how callers can check for specific sentinel errors using direct equality comparison.

    Read the item name and purchase quantity from input. Create an inventory, add the item "laptop" with quantity 5, then attempt the purchase. Based on the error returned, print a specific message showing that you identified which sentinel error occurred.

The following inputs will be provided:

  • Line 1: Item name to purchase
  • Line 2: Quantity to purchase

Handle the result by checking which specific error was returned:

  • If err == ErrItemNotFound: print Error: Item '[name]' does not exist in our inventory
  • If err == ErrOutOfStock: print Error: Item '[name]' is currently out of stock
  • If err == ErrInsufficientQuantity: print Error: Cannot purchase [quantity] units of '[name]' - not enough in stock
  • If successful: print Successfully purchased [quantity] units of '[name]'

For example, given laptop and 3, your output should be:

Successfully purchased 3 units of 'laptop'

And given phone and 2, your output should be:

Error: Item 'phone' does not exist in our inventory

And given laptop and 10, your output should be:

Error: Cannot purchase 10 units of 'laptop' - not enough in stock

Cheat sheet

A sentinel error is a predefined, package-level error variable that represents a specific error condition. Sentinel errors are declared once and reused throughout your code.

Define sentinel errors as package-level variables, typically with names starting with Err:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

Functions return these predefined errors when specific conditions occur:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

Callers can check for specific error conditions by comparing error values directly:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Go's standard library uses sentinel errors extensively, such as io.EOF and sql.ErrNoRows.

Try it yourself

package main

import (
	"fmt"
)

func main() {
	// Read input
	var itemName string
	var quantity int
	fmt.Scanln(&itemName)
	fmt.Scanln(&quantity)

	// Create inventory and add "laptop" with quantity 5
	inv := NewInventory()
	inv.AddItem("laptop", 5)

	// TODO: Attempt to purchase the item
	// Call inv.Purchase(itemName, quantity)

	// TODO: Check which specific sentinel error was returned using direct equality comparison
	// Use if/else if statements to check:
	// - err == ErrItemNotFound
	// - err == ErrOutOfStock
	// - err == ErrInsufficientQuantity
	// - err == nil (success)

	// TODO: Print the appropriate message based on the error
	// If ErrItemNotFound: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
	// If ErrOutOfStock: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
	// If ErrInsufficientQuantity: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
	// If success: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming