Go Panic and Recover

Panic is Go's mechanism for handling unrecoverable errors — situations where the program cannot continue safely. Recover allows a program to catch a panic before it crashes the entire application. Together they form Go's alternative to traditional exception handling.

What Is Panic?

A panic stops the normal execution of the current function immediately. Go then unwinds the call stack, running any deferred functions along the way, and finally crashes the program with an error message.

package main

import "fmt"

func main() {
    fmt.Println("Start")
    panic("something went terribly wrong")
    fmt.Println("This line never runs")
}

Output:

Start
goroutine 1 [running]:
main.main()
    /tmp/main.go:6 +0x68
panic: something went terribly wrong

Panic Flow Diagram

Normal execution →  panic() is called
                         │
                         ▼
          Stop current function immediately
                         │
                         ▼
          Run all deferred functions (in LIFO order)
                         │
                         ▼
          Propagate up the call stack
                         │
                         ▼
          Program crashes with panic message
          (unless recovered)

When Does Go Panic Automatically?

Go panics on its own when the program performs illegal operations.

SituationExample
Accessing an out-of-range slice indexs[10] on a slice of length 3
Dereferencing a nil pointer(*p).Name when p is nil
Dividing by zerox / 0 with integer types
Closing a closed channelClosing the same channel twice

What Is Recover?

Recover stops a panic from crashing the program. It only works inside a deferred function. When called inside a deferred function during a panic, it catches the panic value and allows execution to continue normally after the panicking function returns.

package main

import "fmt"

func safeDivide(a, b int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    result := a / b
    fmt.Println("Result:", result)
}

func main() {
    safeDivide(10, 2)  // works fine
    safeDivide(10, 0)  // panics, but recover catches it
    fmt.Println("Program continues after recovery")
}

Output:

Result: 5
Recovered from panic: runtime error: integer divide by zero
Program continues after recovery

Recover Must Be in a Deferred Function

// WRONG – recover here does nothing
func bad() {
    r := recover()  // not deferred, useless outside a defer
    fmt.Println(r)
}

// CORRECT – recover works only inside defer
func good() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("caught:", r)
        }
    }()
    panic("test")
}

Panic with a Custom Message

package main

import "fmt"

func validateAge(age int) {
    if age < 0 {
        panic(fmt.Sprintf("invalid age: %d — age cannot be negative", age))
    }
    fmt.Println("Valid age:", age)
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Error caught:", r)
        }
    }()

    validateAge(25)   // Valid age: 25
    validateAge(-5)   // triggers panic, caught by recover
}

Output:

Valid age: 25
Error caught: invalid age: -5 — age cannot be negative

Panic vs Error Handling

SituationUse
Expected failures (file not found, bad input)Return an error value
Programming bugs (nil pointer, out-of-range)Let panic occur
Server must not crash on any requestUse recover in middleware

Key Points

  • Panic immediately stops the current function and begins unwinding the call stack
  • Deferred functions always run during a panic before the program crashes
  • Recover catches a panic and allows the program to continue — only works inside a deferred function
  • Use regular error returns for expected failures; reserve panic for genuine programming bugs
  • Web servers commonly use recover in middleware to prevent one bad request from crashing the whole server

Leave a Comment