Panic & Recover
While Go prefers explicit error handling, it provides
panic and
recover for exceptional situations. In this lesson, you'll learn
when to use
panic, how to recover from panics, and best practices for error handling in Go.
What is Panic?
A panic is a runtime error that stops the normal execution of a program. When a function panics:
- The function stops executing
- Deferred functions are executed
- The panic propagates up the call stack
- The program crashes (unless recovered)
Click Run to execute your code
When to Use Panic
| Use Panic For | Use Error For |
|---|---|
| Unrecoverable errors (out of memory) | Expected failures (file not found) |
| Programming errors (nil pointer) | Validation errors |
| Initialization failures | Network errors |
| Impossible situations | User input errors |
Recovering from Panic
The recover function stops a panic and returns the value passed to
panic:
Click Run to execute your code
recoveronly works inside deferred functions- Returns
nilif there's no panic - Returns the panic value if recovering from a panic
- Stops the panic from propagating
Panic vs Errors
Go philosophy: Errors are values, not exceptions
β Don't Use Panic
// Bad: Using panic for expected errors
func divide(a, b float64) float64 {
if b == 0 {
panic("division by zero") // Don't do this!
}
return a / b
}
β Use Errors Instead
// Good: Return error for expected failures
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}
Practical Patterns
1. Protecting Against Panics in Goroutines
func safeGoroutine(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in goroutine:", r)
}
}()
fn()
}()
}
func main() {
safeGoroutine(func() {
panic("Something went wrong!")
})
time.Sleep(time.Second)
fmt.Println("Main continues...")
}
2. Cleanup with Defer and Recover
func processFile(filename string) (err error) {
f, err := os.Open(filename)
if err != nil {
return err
}
defer func() {
f.Close()
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
// Process file (might panic)
// ...
return nil
}
3. Must Functions (Initialization)
// Common pattern: "Must" functions panic on error
func MustCompile(pattern string) *regexp.Regexp {
re, err := regexp.Compile(pattern)
if err != nil {
panic(err)
}
return re
}
// Used during initialization
var emailRegex = MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
Understanding Stack Traces
When a panic occurs, Go prints a stack trace:
func level3() {
panic("Something went wrong!")
}
func level2() {
level3()
}
func level1() {
level2()
}
func main() {
level1()
}
// Output shows the call stack:
// panic: Something went wrong!
//
// goroutine 1 [running]:
// main.level3()
// /path/to/file.go:4
// main.level2()
// /path/to/file.go:8
// main.level1()
// /path/to/file.go:12
// main.main()
// /path/to/file.go:16
Common Mistakes
1. Using panic for normal errors
// β Wrong - panic for expected errors
func readConfig(filename string) Config {
data, err := os.ReadFile(filename)
if err != nil {
panic(err) // Don't panic for file errors!
}
// ...
}
// β
Correct - return error
func readConfig(filename string) (Config, error) {
data, err := os.ReadFile(filename)
if err != nil {
return Config{}, err
}
// ...
}
2. Recover outside defer
// β Wrong - recover doesn't work here
func bad() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
panic("Error!")
}
// β
Correct - recover in defer
func good() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
panic("Error!")
}
3. Swallowing panics silently
// β Wrong - hiding panics
func bad() {
defer func() {
recover() // Silently ignores panic!
}()
// ...
}
// β
Correct - log or handle panic
func good() {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic recovered: %v", r)
// Take appropriate action
}
}()
// ...
}
Exercise: Safe Division Calculator
Task: Create a safe calculator that handles panics gracefully.
Requirements:
- Create a
safeDividefunction that uses recover - Handle division by zero with recover
- Return both result and error
- Test with various inputs including zero
Show Solution
package main
import (
"fmt"
)
// safeDivide performs division with panic recovery
func safeDivide(a, b float64) (result float64, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
}
}()
if b == 0 {
panic("division by zero")
}
result = a / b
return result, nil
}
// Better approach: use errors instead of panic
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
func main() {
// Test safeDivide
fmt.Println("=== Using safeDivide (with panic/recover) ===")
result, err := safeDivide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", result)
}
result, err = safeDivide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("10 / 0 = %.2f\n", result)
}
// Test divide (better approach)
fmt.Println("\n=== Using divide (with errors) ===")
result, err = divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", result)
}
result, err = divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("10 / 0 = %.2f\n", result)
}
}
Summary
- panic stops normal execution and unwinds the stack
- recover catches panics and returns the panic value
- recover only works inside deferred functions
- Use errors, not panic for expected failures
- Panic is for exceptional situations only
- Deferred functions run even during a panic
- Must functions use panic for initialization errors
- Always log recovered panics for debugging
What's Next?
Congratulations on completing the Functions module! You now understand functions, closures, defer, panic, and recover. Next, you'll learn about Data Structures, starting with arrays and slicesβGo's powerful collection types.
Enjoying these tutorials?