Go Cheatsheet
Error Handling
Use this Go reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
The error Interface
error is a built-in interface with one method.
type error interface { Error() string }
Return nil for success, a non-nil error on failure. Always handle errors — Go has no exceptions.
Returning Errors
func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } result, err := divide(10, 0) if err != nil { log.Fatal(err) } fmt.Println(result)
errors Package
import "errors" // Create a simple error err := errors.New("something went wrong") // Wrap an error (adds context) wrapped := fmt.Errorf("open config: %w", err) // Unwrap — get the inner error inner := errors.Unwrap(wrapped) // Is — check if any error in the chain matches a target errors.Is(wrapped, err) // true errors.Is(err, fs.ErrNotExist) // check standard errors // As — extract a specific type from the chain var pe *os.PathError if errors.As(err, &pe) { fmt.Println("path:", pe.Path) }
fmt.Errorf with %w
%w wraps the original error so it is reachable via errors.Is/errors.As.
func readConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("readConfig %s: %w", path, err) } _ = data return nil } err := readConfig("/missing.json") errors.Is(err, os.ErrNotExist) // true — walks the chain
%vembeds the message but does NOT wrap (chain is broken);%wwraps.
Sentinel Errors
Package-level error variables that callers can compare.
var ( ErrNotFound = errors.New("not found") ErrPermission = errors.New("permission denied") ErrTimeout = errors.New("operation timed out") ) func lookup(id int) (User, error) { if id <= 0 { return User{}, ErrNotFound } // ... } u, err := lookup(-1) if errors.Is(err, ErrNotFound) { // handle missing }
Custom Error Types
Implement the error interface on your own struct for structured errors.
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 validate(name string) error { if name == "" { return &ValidationError{Field: "name", Message: "must not be empty"} } return nil } err := validate("") var ve *ValidationError if errors.As(err, &ve) { fmt.Println(ve.Field, ve.Message) }
Wrapping Multiple Errors (Go 1.20+)
errors.Join combines multiple errors into one. Each is reachable via errors.Is/errors.As.
import "errors" err1 := errors.New("db failed") err2 := errors.New("cache failed") combined := errors.Join(err1, err2) fmt.Println(combined) // db failed\ncache failed errors.Is(combined, err1) // true errors.Is(combined, err2) // true // fmt.Errorf with multiple %w (Go 1.20+) err := fmt.Errorf("two failures: %w and %w", err1, err2) errors.Is(err, err1) // true
Panic and Recover
panic is not for normal errors. Use it for unrecoverable states or invariant violations.
func mustPositive(n int) int { if n <= 0 { panic(fmt.Sprintf("expected positive, got %d", n)) } return n } // Recover in a deferred function func safeRun(f func()) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered panic: %v", r) } }() f() return nil } err := safeRun(func() { mustPositive(-1) }) fmt.Println(err) // recovered panic: expected positive, got -1
Printing a stack trace inside recover
import "runtime/debug" defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "panic: %v\n%s\n", r, debug.Stack()) } }()
log Package (for Fatal errors)
import "log" log.Print("message") log.Printf("value: %v", v) log.Println("done") log.Fatal("fatal: exits with code 1") // calls os.Exit(1) after logging log.Fatalf("fatal: %v", err) log.Panic("panic: then panics") // logs then calls panic()
Structured logging (Go 1.21+ slog)
import "log/slog" slog.Info("user created", "id", 42, "name", "Alice") slog.Error("request failed", "err", err, "url", url) slog.Warn("quota exceeded", "used", 900, "limit", 1000) slog.Debug("cache miss", "key", key) // JSON handler logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) logger.Info("started", "addr", ":8080") logger.With("requestID", "abc123").Info("handling request")
Idiomatic Error Handling Patterns
Early return
func process(path string) error { f, err := os.Open(path) if err != nil { return fmt.Errorf("open: %w", err) } defer f.Close() data, err := io.ReadAll(f) if err != nil { return fmt.Errorf("read: %w", err) } if err := parse(data); err != nil { return fmt.Errorf("parse: %w", err) } return nil }
errgroup (parallel error collection)
import "golang.org/x/sync/errgroup" g, ctx := errgroup.WithContext(context.Background()) g.Go(func() error { return fetchUser(ctx, id) }) g.Go(func() error { return fetchOrders(ctx, id) }) if err := g.Wait(); err != nil { return err // first non-nil error }
Inline error checking helper
// For scripts or CLI tools where every error is fatal func must[T any](v T, err error) T { if err != nil { panic(err) } return v } data := must(os.ReadFile("config.json"))
Standard Error Variables
| Sentinel | Package | Meaning |
|---|---|---|
io.EOF | io | end of file (expected) |
io.ErrUnexpectedEOF | io | EOF in middle of data |
io.ErrClosedPipe | io | write to closed pipe |
os.ErrNotExist | os | file not found |
os.ErrExist | os | file already exists |
os.ErrPermission | os | permission denied |
os.ErrDeadlineExceeded | os | I/O deadline exceeded |
context.Canceled | context | context was canceled |
context.DeadlineExceeded | context | context deadline exceeded |
sql.ErrNoRows | database/sql | query returned no rows |
http.ErrServerClosed | net/http | server shutdown gracefully |
Gotchas
// 1. Typed nil interface is not a nil error func badFunc() error { var p *MyError = nil return p // returns non-nil error! Interface wraps (type=*MyError, value=nil) } // Fix: return nil explicitly // 2. errors.Is uses == for comparisons; for custom types, implement Is() func (e *MyError) Is(target error) bool { t, ok := target.(*MyError) return ok && t.Code == e.Code } // 3. errors.As requires a pointer-to-pointer var ve *ValidationError errors.As(err, &ve) // correct // errors.As(err, ve) // wrong: not a pointer to the concrete type // 4. Don't ignore errors os.Remove("tmp") // WRONG: ignoring error _ = os.Remove("tmp") // explicit ignore (acceptable when truly fine) if err := os.Remove("tmp"); err != nil { ... } // best // 5. Add context at each layer; avoid repeating error text // Bad: fmt.Errorf("failed to read file: %w", err) where err already says "no such file" // Good: fmt.Errorf("load config %s: %w", path, err)