Lesson 6 • Intermediate
Error Handling 🐹
Master Go's explicit error handling — return errors as values, wrap them with context, and build custom error types for robust applications.
What You'll Learn in This Lesson
- • Go's "errors as values" philosophy
- • Custom error types and sentinel errors
- • Error wrapping with
%wanderrors.Is/As - • Error handling patterns (early return, wrapping chain)
- • Best practices and the decision tree
1️⃣ Error Basics
Go treats errors as ordinary values returned from functions. The if err != nil pattern is the cornerstone of Go error handling — verbose, but explicit and impossible to accidentally ignore.
Try It: Error Basics
The error return pattern and why Go chose it
// Error Handling in Go — Explicit Returns
console.log("=== Go's Error Philosophy ===");
console.log("Go treats errors as values, not exceptions.");
console.log("Functions return an error as the LAST return value.");
console.log("Callers MUST check the error before using the result.");
console.log();
console.log("=== The Pattern ===");
console.log(" result, err := doSomething()");
console.log(" if err != nil {");
console.log(" // handle error");
console.log(" return err");
console.l
...2️⃣ Custom Errors & Wrapping
Create custom error types for rich error information. Use fmt.Errorf with %w to wrap errors with context. Check wrapped errors with errors.Is and errors.As.
Try It: Custom Errors
Sentinel errors, custom types, and error wrapping
// Custom Error Types
console.log("=== The error Interface ===");
console.log(" type error interface {");
console.log(" Error() string");
console.log(" }");
console.log(" // Any type with an Error() method is an error!");
console.log();
console.log("=== Custom Error Type ===");
console.log(" type ValidationError struct {");
console.log(" Field string");
console.log(" Message string");
console.log(" }");
console.log();
console.log(" func (e *ValidationError) Error() string
...3️⃣ Error Handling Patterns
Early returns keep the happy path unindented. Error wrapping chains add context at each level. Error groups handle concurrent errors gracefully.
Try It: Patterns
Early return, wrapping chains, and error groups
// Error Handling Patterns
console.log("=== Pattern 1: Early Return ===");
console.log(" func processFile(path string) error {");
console.log(" f, err := os.Open(path)");
console.log(" if err != nil {");
console.log(' return fmt.Errorf("open file: %w", err)');
console.log(" }");
console.log(" defer f.Close()");
console.log();
console.log(" data, err := io.ReadAll(f)");
console.log(" if err != nil {");
console.log(' return fmt.Errorf("read file: %w
...4️⃣ Best Practices
Follow Go's error handling conventions: always check errors, add context when wrapping, log at the top level only, and use the decision tree to choose the right approach.
Try It: Best Practices
Do's, don'ts, and the error decision tree
// Error Handling Best Practices
console.log("=== Do's and Don'ts ===");
console.log();
const dos = [
"Always check errors — never use _ for error returns in production",
"Add context when wrapping: fmt.Errorf('read config: %w', err)",
"Return errors to the caller — let them decide how to handle",
"Use sentinel errors for expected conditions (ErrNotFound)",
"Use custom error types when callers need to inspect details",
"Log errors at the TOP level, not at every layer",
];
const don
...⚠️ Common Mistakes
return fmt.Errorf("%w", err) adds nothing. Always add what operation failed.errors.Is instead of == to check errors. It traverses the entire wrap chain.📋 Quick Reference
| Pattern | Go Syntax |
|---|---|
| Create error | errors.New("msg") |
| Wrap error | fmt.Errorf("ctx: %w", err) |
| Check type | errors.Is(err, ErrNotFound) |
| Extract type | errors.As(err, &target) |
| Handle | if err != nil { return err } |
🎉 Lesson Complete!
You've mastered Go's error handling! Next, we'll build HTTP servers and REST APIs with Go's standard library.
Sign up for free to track which lessons you've completed and get learning reminders.