Skip to main content

Overview

Go makes it possible to recover from a panic by using the recover built-in function. A recover can stop a panic from aborting the program and let it continue with execution instead.

Why Use Recover

An example of where this can be useful: a server wouldn’t want to crash if one of the client connections exhibits a critical error. Instead, the server would want to close that connection and continue serving other clients.
In fact, this is what Go’s net/http package does by default for HTTP servers - it recovers from panics in handlers to keep the server running.

Basic Recover Usage

recover must be called within a deferred function. When the enclosing function panics, the defer will activate and a recover call within it will catch the panic:
func main() {
    defer func() {
        if r := recover(); r != nil {
            // The return value of recover is the error raised in
            // the call to panic
            fmt.Println("Recovered. Error:\n", r)
        }
    }()

    mayPanic()

    // This code will not run, because mayPanic panics.
    // The execution of main stops at the point of the
    // panic and resumes in the deferred closure.
    fmt.Println("After mayPanic()")
}
recover only works when called directly inside a deferred function. It will not catch panics if called in a regular function or in a deferred function’s nested calls.

Complete Example

package main

import "fmt"

// This function panics
func mayPanic() {
	panic("a problem")
}

func main() {
	// recover must be called within a deferred function.
	// When the enclosing function panics, the defer will
	// activate and a recover call within it will catch
	// the panic.
	defer func() {
		if r := recover(); r != nil {
			// The return value of recover is the error raised in
			// the call to panic
			fmt.Println("Recovered. Error:\n", r)
		}
	}()

	mayPanic()

	// This code will not run, because mayPanic panics.
	// The execution of main stops at the point of the
	// panic and resumes in the deferred closure.
	fmt.Println("After mayPanic()")
}

Output

$ go run recover.go
Recovered. Error:
 a problem

How Recover Works

The recover function:
  1. Returns nil in normal execution - When there’s no panic, recover returns nil
  2. Returns panic value during panic - When called during a panic, it returns the value passed to panic
  3. Stops panic propagation - Calling recover stops the panic from unwinding the stack further
  4. Must be in deferred function - Only works when called directly inside a defer

Practical Example: Safe Goroutine Execution

func safeGoroutine(fn func()) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("Goroutine panicked: %v", r)
            }
        }()
        fn()
    }()
}

// Usage
safeGoroutine(func() {
    // This panic won't crash the program
    panic("unexpected error")
})

Web Server Example

func handler(w http.ResponseWriter, r *http.Request) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Handler panicked: %v", r)
            http.Error(w, "Internal Server Error", 500)
        }
    }()
    
    // Handler code that might panic
    processRequest(r)
}

When to Use Recover

Recover from panics in individual request handlers or goroutines to keep the server running, rather than letting one error crash the entire service.
Use recover at the top level of exported functions in libraries to prevent internal panics from crashing the caller’s program unexpectedly.
When executing third-party or user-provided code, recover can protect your application from panics in that code.
Don’t use panic/recover as a substitute for normal error handling with error return values. Use it only for truly exceptional situations.

Recover Patterns

Pattern 1: Logging and Re-panicking

defer func() {
    if r := recover(); r != nil {
        log.Printf("Panic occurred: %v", r)
        // Log the error but let it continue panicking
        panic(r)
    }
}()

Pattern 2: Converting Panic to Error

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

Pattern 3: Cleanup Before Propagating

defer func() {
    if r := recover(); r != nil {
        // Perform cleanup
        cleanup()
        // Re-panic to let caller handle it
        panic(r)
    }
}()

Best Practices

Use in deferred functions only

recover only works when called directly inside a deferred function.

Check for nil

Always check if recover() returns nil before assuming a panic occurred.

Log panic information

Log the panic value and stack trace for debugging when recovering.

Don't overuse

Use recover sparingly - prefer normal error handling for expected failures.

Common Mistakes

Recover doesn’t work in nested function calls

// Wrong: recover is not in the deferred function
defer helperFunc()

func helperFunc() {
    if r := recover(); r != nil {
        // This won't catch panics!
    }
}

// Correct: recover is directly in the deferred function
defer func() {
    if r := recover(); r != nil {
        // This works!
    }
}()

Getting Stack Traces

When recovering from a panic, it’s useful to capture the stack trace:
import (
    "fmt"
    "runtime/debug"
)

defer func() {
    if r := recover(); r != nil {
        fmt.Printf("Panic: %v\n", r)
        fmt.Printf("Stack trace:\n%s\n", debug.Stack())
    }
}()

Testing Functions That Panic

In tests, you can verify that a function panics as expected:
func TestFunctionPanics(t *testing.T) {
    defer func() {
        if r := recover(); r == nil {
            t.Error("Expected function to panic")
        }
    }()
    
    // Call function that should panic
    functionThatShouldPanic()
}

Panic, Defer, and Recover Together

These three mechanisms work together to provide robust error handling:
func example() {
    defer fmt.Println("Cleanup happens") // Runs even if panic occurs
    
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from:", r)
        }
    }()
    
    panic("something went wrong")
    
    fmt.Println("This never executes")
}

// Output:
// Recovered from: something went wrong
// Cleanup happens

Panic

Learn when and how to use panic in Go.

Defer

Understand how defer enables recover to work.

Errors

Master Go’s standard error handling approach.

Build docs developers (and LLMs) love