Recover is a built-in function in Go that regains control of a panicking goroutine. It is only useful inside deferred functions and allows a program to handle a panic gracefully instead of crashing. When called during a panic, recover stops the panicking sequence and returns the value passed to panic.
💡 Key Points
- Recover only works when called inside a deferred function
- Returns nil if the goroutine is not panicking
- Returns the value passed to panic if called during a panic
- Calling recover stops the panic and resumes normal execution
- Recover only catches panics in the same goroutine
- Use recover to convert panics into errors at API boundaries
- Always check if recover returned non-nil before taking action
Basic Recover Usage
Recover must be called within a deferred function to catch panics:
package main
import "fmt"
func safeDivide(a, b int) int {
defer func() {
if r := recover(); r != nil {
fmt. Println("Recovered from panic:", r)
}
}()
return a / b
}
func main() {
result := safeDivide(10, 2)
fmt.Println("Result:", result)
result = safeDivide(10, 0)
fmt.Println("Result after panic:", result)
fmt.Println("Program continues normally")
}
Output:Result: 5
Recovered from panic: runtime error: integer divide by zero
Result after panic: 0
Program continues normally
Converting Panic to Error
A common pattern is to convert a panic into an error return value:
package main
import (
"fmt"
"errors"
)
func riskyOperation(value int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("operation failed: %v", r)
}
}()
if value < 0 {
panic("negative value not allowed")
}
result = 100 / value
return result, nil
}
func main() {
result, err := riskyOperation(10)
fmt.Printf("Result: %d, Error: %v\n", result, err)
result, err = riskyOperation(0)
fmt.Printf("Result: %d, Error: %v\n", result, err)
result, err = riskyOperation(-5)
fmt.Printf("Result: %d, Error: %v\n", result, err)
}
Output:Result: 10, Error: <nil>
Result: 0, Error: operation failed: runtime error: integer divide by zero
Result: 0, Error: operation failed: negative value not allowed
Selective Recovery
You can choose to recover only specific types of panics and re-panic others:
package main
import "fmt"
type ValidationError struct {
Message string
}
func process(value int) {
defer func() {
if r := recover(); r != nil {
// Only recover from ValidationError
if _, ok := r.(ValidationError); ok {
fmt.Println("Recovered from validation error:", r)
} else {
// Re-panic for other errors
panic(r)
}
}
}()
if value < 0 {
panic(ValidationError{Message: "value must be positive"})
}
if value > 100 {
panic("value too large") // This will not be caught
}
fmt. Println("Processing value:", value)
}
func main() {
process(50)
process(-10)
fmt.Println("Done")
}
Output:Processing value: 50
Recovered from validation error: {value must be positive}
Done
Recover Behavior
| Context | Return Value | Effect |
| During panic in same goroutine | Panic value | Stops panic, resumes execution |
| Not during panic | nil | No effect |
| Called directly (not in defer) | nil | No effect, panic continues |
| Different goroutine's panic | nil | Cannot catch other goroutine's panic |
| Nested defer with recover | First recover catches it | Only outermost defer's recover executes |
Common Recover Patterns
| Pattern | Code | Use Case |
| Basic Recovery | defer func() { if r := recover(); r != nil { log.Println(r) } }() | Log panic and continue |
| Return Error | defer func() { if r := recover(); r != nil { err = fmt.Errorf("%v", r) } }() | Convert panic to error |
| With Stack Trace | defer func() { if r := recover(); r != nil { log.Printf("%v\n%s", r, debug.Stack()) } }() | Log panic with full stack trace |
| Cleanup and Recover | defer func() { cleanup(); if r := recover(); r != nil { handle(r) } }() | Ensure cleanup before handling panic |
| Type-Based Recovery | defer func() { if r := recover(); r != nil { if err, ok := r.(MyError); ok { ... } } }() | Handle specific error types |
| Re-panic | defer func() { if r := recover(); r != nil { cleanup(); panic(r) } }() | Cleanup then propagate panic |
| Goroutine Safety | go func() { defer func() { recover() }(); doWork() }() | Prevent goroutine panic from crashing app |
Recover Use Cases
| Scenario | Should Use Recover? | Reason |
| Web server handler | ✅ Yes | Prevent one request panic from crashing server |
| Worker goroutine | ✅ Yes | Prevent worker crash from affecting system |
| API package boundary | ✅ Yes | Convert internal panics to errors for callers |
| Plugin/extension code | ✅ Yes | Isolate third-party code failures |
| Development/testing | ❌ No | Let panics surface to reveal bugs |
| Application initialization | ❌ No | Fatal errors should stop the program |
| Library internal code | ❌ Usually No | Fix bugs instead of hiding them |
Recover Best Practices
| Practice | Description | Example |
| Always Check for nil | Only act if recover returns non-nil | if r := recover(); r != nil { ... } |
| Use in Defer Only | Recover only works in deferred functions | defer func() { recover() }() |
| Log Recovered Panics | Always log when recovering from panic | Use structured logging with context |
| Include Stack Traces | Capture stack trace for debugging | debug.Stack() in recover handler |
| Protect Goroutines | Every goroutine should have recover | Wrap goroutine body in defer/recover |
| Don't Ignore Panics | Silent recovery hides bugs | Always log or convert to error |
| Clean Up Resources | Ensure cleanup before/after recover | Close files, release locks, etc. |
| Avoid Over-Recovery | Don't recover everywhere | Let panics fail fast during development |
Recover Gotchas
| Issue | Problem | Solution |
| Not in Defer | r := recover() returns nil | Always call recover inside defer function |
| Wrong Goroutine | Cannot catch panic from another goroutine | Each goroutine needs its own recover |
| Nested Defers | Only one recover catches the panic | First recover in defer chain wins |
| Silent Recovery | defer func() { recover() }() hides errors | Always check and log recovered values |
| Re-panic Mistakes | panic(recover()) may panic with nil | Check for nil before re-panicking |
| Resource Leaks | Recovered panic may leave resources open | Ensure cleanup in separate defers |