Panic is a built-in function in Go that stops normal execution flow and begins panicking. When a function panics, it immediately stops executing, runs any deferred functions, and then returns to its caller. This process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes.
💡 Key Points
- Panic stops normal execution and unwinds the stack
- Deferred functions still execute during a panic
- Use panic for truly unrecoverable errors
- Prefer returning errors over panicking in most cases
- Panic can be recovered using
recover() in deferred functions - Unrecovered panics crash the program with a stack trace
- Panics in goroutines only affect that goroutine (unless unrecovered)
Basic Panic Usage
A panic is typically caused by programming errors that should not occur:
package main
import "fmt"
func main() {
fmt.Println("starting")
panic("something bad happened")
fmt.Println("this will not print")
}
Output:starting
panic: something bad happened
goroutine 1 [running]:
main.main()
/path/to/file.go:6 +0x...
Panic with Defer
Deferred functions execute even when a panic occurs:
package main
import "fmt"
func main() {
defer fmt.Println("deferred call 1")
defer fmt.Println("deferred call 2")
fmt.Println("about to panic")
panic("panic occurred")
fmt.Println("this will not execute")
}
Output:about to panic
deferred call 2
deferred call 1
panic: panic occurred
goroutine 1 [running]:
main. main()
/path/to/file.go:9 +0x...
Recovering from Panic
The recover function can catch a panic and resume normal execution:
package main
import "fmt"
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt. Println("recovered from panic:", r)
result = 0
}
}()
result = a / b
return result
}
func main() {
fmt.Println("result:", safeDivide(10, 2))
fmt.Println("result:", safeDivide(10, 0))
fmt.Println("program continues normally")
}
Output:result: 5
recovered from panic: runtime error: integer divide by zero
result: 0
program continues normally
When to Panic
| Scenario | Use Panic? | Reason |
| Unrecoverable errors during init | ✅ Yes | Cannot continue if setup fails |
| Programming errors (bugs) | ✅ Yes | Indicates code needs fixing |
| Impossible state reached | ✅ Yes | Logic error in code |
| Expected errors (file not found) | ❌ No | Return error instead |
| User input validation failures | ❌ No | Expected scenario, return error |
| Library/API code | ❌ No | Let caller decide how to handle |
Panic Functions and Methods
| Function | Signature | Description |
| panic | panic(v interface{}) | Stops execution and begins panicking |
| recover | recover() interface{} | Regains control of panicking goroutine |
Common Panic Causes
| Cause | Example | Prevention |
| Nil pointer dereference | var p *int; fmt.Println(*p) | Check for nil before dereferencing |
| Index out of range | arr := []int{1,2,3}; arr[5] | Check length before accessing |
| Type assertion failure | var i interface{} = "hello"; i.(int) | Use comma-ok idiom: v, ok := i.(int) |
| Close on closed channel | close(ch); close(ch) | Track channel state, close once |
| Send on closed channel | close(ch); ch <- 1 | Coordinate sender/receiver properly |
| Division by zero | x / 0 | Check divisor before operation |
| Map concurrent access | Concurrent read/write to map | Use sync.Map or mutex protection |
Panic and Recover Patterns
| Pattern | Code | Use Case |
| Basic Recovery | defer func() { if r := recover(); r != nil { log.Println(r) } }() | Log and continue |
| Convert to Error | defer func() { if r := recover(); r != nil { err = fmt.Errorf("%v", r) } }() | Convert panic to error return |
| Selective Recovery | defer func() { if r := recover(); r != myError { panic(r) } }() | Only catch specific panics |
| Cleanup and Re-panic | defer func() { cleanup(); panic(recover()) }() | Cleanup resources then propagate panic |
| Goroutine Protection | go func() { defer recover(); ... }() | Prevent goroutine panic from crashing program |
| Assert Invariants | if !condition { panic("invariant violated") } | Check programming assumptions |
Panic Best Practices
| Practice | Description | Example |
| Prefer Errors | Return errors instead of panicking | return nil, errors.New("failed") not panic(... ) |
| Document Panics | Document when functions may panic | Comment: "Panics if index out of range" |
| Recover in Goroutines | Always recover in goroutines you start | Prevents unhandled panic crashes |
| Recover at Boundaries | Recover at package/API boundaries | Convert to errors for callers |
| Don't Recover Everywhere | Let panics crash during development | Reveals bugs that need fixing |
| Stack Traces | Include stack trace when recovering | debug.Stack() for context |