Explain panic and recover — how do they differ from exceptions, and when should you use them?

5 minintermediategopanicrecovererror-handling

Quick Answer

panic immediately stops normal execution of the current function, runs any deferred calls on the way up, and propagates up the call stack until either a deferred function calls recover (stopping the unwind and letting the program continue), or the panic reaches main and crashes the whole program with a stack trace. Unlike exceptions in Java/Python, panic/recover is not meant for ordinary error handling — idiomatic Go reserves it for truly unrecoverable situations (a programming bug like an out-of-bounds index, or a genuinely unexpected invariant violation), while expected, recoverable failures (a missing file, a failed network call) should use the normal error return value instead. recover only works when called directly inside a deferred function; calling it anywhere else is a no-op.

Detailed Answer

Go has both error values and panic/recover. Idiomatic code uses them for very different situations.

Basic panic and recover

func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    return a / b, nil  // integer divide by zero panics
}

result, err := safeDivide(10, 0)
fmt.Println(result, err) // 0, "recovered from panic: runtime error: integer divide by zero"

Why recover must be called directly in a defer

func broken() {
    defer func() {
        recover()          // works
    }()
    panic("boom")
}

func alsoBroken() {
    defer helper()          // does NOT work — recover() called inside a nested function does nothing here
    panic("boom")
}
func helper() {
    recover()
}

recover() only stops the panic if it's called directly by a deferred function while a panic is actively unwinding through that exact function's frame. One extra level of indirection breaks it.

When to reach for panic vs. error

SituationUse
A file might not existerror — expected, recoverable
A network call might time outerror — expected, recoverable
An out-of-bounds slice indexpanic (automatic, from the runtime)
A programming invariant is violated (e.g., "this should never be nil here")panic, deliberately, as a fail-fast signal
A library function's required argument is missingpanic at the API boundary, often, e.g. template.Must

The idiomatic guidance

Reserve panic for bugs and truly unrecoverable conditions, not for things a caller should reasonably expect to happen and handle. Libraries that panic on ordinary, expected failure conditions are considered poor Go API design. Callers should get an error they can check, not a program crash they have to guard against.

Related Resources