>_
GolangStepByStep
Intern

Panic & Recover

Handle unrecoverable errors and recover from panics safely

# Why panic and recover Matter

In Go, error is for expected failures, while panicis for programmer bugs and impossible states.

recover lets you stop a panic from crashing the process, usually at boundaries like HTTP middleware, worker loops, and goroutine entry points.

Real-World Analogyclick to expand

Think of panic as a circuit breaker tripping: execution stops immediately. recover is a control panel reset done by an operator in a safe boundary area.

Inside business logic, you return errors. At boundaries, you recover to keep the whole service alive.

# panic Basics

A panic unwinds the current goroutine stack, running deferred functions in LIFO order.

func divide(a, b int) int {
    if b == 0 {
        panic("cannot divide by zero")
    }
    return a / b
}

# recover Basics

recover() only works when called directly inside a deferred function.

func safeRun(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic caught: %v", r)
        }
    }()
    fn()
    return nil
}

# Step-by-Step Rules

Use panic for invariants, not expected errors

Validation, I/O, and network failures should return error values.

recover must be in defer on same goroutine

A panic cannot be recovered from another goroutine.

panic still runs deferred cleanup

This is why defer is critical around locks/files even in code that might panic.

# panic vs error in Practice

// Expected failure -> error return
func readFile(path string) ([]byte, error) { ... }

// Impossible state -> panic
func mustBePositive(n int) {
    if n <= 0 {
        panic(fmt.Sprintf("BUG: expected positive, got %d", n))
    }
}

// Boundary recovery middleware
defer func() {
    if r := recover(); r != nil {
        http.Error(w, "Internal Server Error", 500)
    }
}()

# Real-World Example: Must Helper

Must is useful in initialization code where failure should stop startup.

package main

import (
    "fmt"
    "strconv"
)

func Must[T any](v T, err error) T {
    if err != nil {
        panic(err)
    }
    return v
}

func main() {
    n := Must(strconv.Atoi("42"))
    fmt.Println(n)

    n = Must(strconv.Atoi("not a number"))
    fmt.Println(n)
}

⚡ Key Takeaways

  • Use error for expected failures and panic for invariant violations
  • recover() only works in deferred functions on the same goroutine
  • Panics unwind stack and run deferred calls in LIFO order
  • Recover at boundaries (HTTP middleware, worker loops), not in every function
  • Must helpers are useful for startup-time unrecoverable failures
practice & review