People search golang panic, go panic, or panic golang when something blew up at runtime; searches like go catch panic, golang catch panic, golang recover from panic, or golang panic recover point at the same mechanism: Go has no try/catch, but you can stop a panic with recover inside a defer. This article explains how unwinding interacts with deferred calls, the recover pattern (go recover panic), and when to prefer returning an error instead. For background on goroutines, see goroutines in Go and concurrency in Go.
Tested with Go 1.24 on Linux.
What golang panic does
panic stops normal execution of the current goroutine and starts stack unwinding: deferred functions run in last-in-first-out order. If nothing calls recover, the runtime prints the panic value and stack trace and the program exits (exit status 2 for many cases). Runtime errors such as indexing an empty slice or a failed type assertion can also panic implicitly.
package main
import "fmt"
func main() {
fmt.Println("start")
for i := 0; i < 5; i++ {
if i == 3 {
panic("stop here")
}
fmt.Println(i)
}
fmt.Println("end")
}start
0
1
2
panic: stop here
goroutine 1 [running]:
...The lines after panic in the same function do not run unless the panic is recovered.
defer during a golang panic
Deferred calls are still scheduled when their surrounding function returns—or when that function’s stack frame unwinds because of a panic. That is why cleanup (Close, Unlock, temporary file removal) belongs in defer even when failures happen.
package main
import "fmt"
func work() {
defer fmt.Println("defer in work")
fmt.Println("before panic")
panic("boom")
fmt.Println("after panic")
}
func main() {
defer fmt.Println("defer in main")
work()
fmt.Println("main continues")
}before panic
defer in work
defer in main
panic: boom
...main continues never prints because the panic was not recovered.
golang recover from panic (go catch panic / golang catch panic)
recover returns the panic value (if any) only while a deferred function is running during unwinding from a panic on the same goroutine. Calling recover elsewhere returns nil. That is the whole story behind golang recover from panic and the usual answer to go catch panic.
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
panic("boom")
fmt.Println("never reached")
}You should see recovered: boom and the program exit successfully; the line after panic is still skipped.
Logging with runtime/debug.Stack
To log a golang panic to stderr or a file, capture the value and stack inside the deferred recover:
package main
import (
"log"
"runtime/debug"
)
func risky() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic: %v\n%s", r, debug.Stack())
}
}()
panic("example")
}
func main() {
risky()
log.Println("after risky")
}For richer logging setups, see Logrus tutorial or changing the default log format.
panic versus returning errors and os.Exit
For expected failures (bad input, I/O errors), return an error and document invariants instead of panicking. Reserve panic for bugs or states you truly cannot continue from inside a package boundary.
os.Exit terminates the process immediately without running deferred functions in other frames; use it sparingly (for example in main after printing help, or in tiny tools). Panic runs defers until recover or program death—see Effective Go — Panic and Defer, Panic, and Recover.
Summary
Golang panic starts unwinding the current goroutine’s stack and runs deferred work until the program exits or a deferred function calls recover. That is how go recover panic and golang panic recover work; there is no separate go catch panic keyword—only the defer-plus-recover idiom. recover is scoped to the panicking goroutine and must run from a deferred call. Prefer error returns for normal control flow; use panic rarely, and understand that os.Exit skips deferred cleanup.

