Intermediate · Pointers, interfaces and errors · topic 20 of 46 · 25 min
panic and recover: only for the truly unexpected
Explanation
What a panic is
A panic is Go's emergency stop. It happens when the program hits something it cannot continue from, like reading index 10 of a 3-item slice, following a nil pointer, or writing to a nil map. You can also call panic yourself. When a panic starts, the current function stops, deferred calls run (in reverse order) up through the calling functions, and then the program exits with a message and a stack trace (the list of function calls that led there).
func main() {
defer fmt.Println("deferred: still runs")
s := []int{1, 2, 3}
i := 5
fmt.Println(s[i])
fmt.Println("never printed")
}
// deferred: still runs
// panic: runtime error: index out of range [5] with length 3
// goroutine 1 [running]: ...Errors versus panics
| Use an error when | A panic is acceptable when |
|---|---|
| The user typed bad input | There is a programming bug: a case that "can never happen" happened |
| A file or network call fails | The program cannot start at all (a required template or regex in the code is invalid) |
| A record is not found | A Must-style helper is used with fixed values you control |
| Anything a caller might reasonably handle | Continuing would corrupt data |
The standard library follows this. regexp.MustCompile panics if the pattern is invalid, because the pattern is usually a fixed string in your code and a bad one is a bug you want to see immediately at start-up. regexp.Compile returns an error, for patterns that come from users.
var pinRe = regexp.MustCompile(`^[1-9][0-9]{5}$`) // fixed pattern: Must is fine
func userPattern(p string) (*regexp.Regexp, error) {
return regexp.Compile(p) // user input: return the error
}recover
recover() stops a panic, but only when called directly inside a deferred function in the goroutine that is panicking. It returns the value passed to panic (or nil if there is no panic). After recovering, the function that deferred it returns normally to its caller.
func safeDivide(a, b int) (q int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
return a / b, nil
}
func main() {
fmt.Println(safeDivide(10, 2)) // 5 <nil>
fmt.Println(safeDivide(1, 0)) // 0 recovered: runtime error: integer divide by zero
}Where recover is used in real code
- HTTP servers: net/http already recovers a panic in a handler, logs it and closes that connection, so one bad request does not kill the server. Many teams also add their own recovery middleware to return a clean 500 response and log details (module 8).
- Worker pools: a long-running worker recovers so one bad job is logged instead of stopping all processing.
- Libraries that use panic internally for deep recursion (like parsers) and convert it to an error at their public boundary.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
panic and recover: only for the truly unexpected · 1/7
A panic is Go's emergency stop for things that should never happen.
Prefer a video by a teacher? Find free videos on “golang panic recover defer when to use” on YouTube ↗
In real life: A job worker that survives a bad record
A nightly job processes lakhs of GST invoices. One corrupted record triggers a nil pointer bug. Without recover, the whole job crashes at 2 a.m. and nothing is processed. With a recover around each job, that one invoice is logged for a developer to look at, and the rest are processed.
package main
import "fmt"
type Invoice struct {
ID string
Buyer *string
}
func process(inv Invoice) {
fmt.Println(inv.ID, "buyer:", *inv.Buyer) // panics if Buyer is nil
}
func safeProcess(inv Invoice) (ok bool) {
defer func() {
if r := recover(); r != nil {
fmt.Println("skipped", inv.ID, "because:", r)
ok = false
}
}()
process(inv)
return true
}
func main() {
b := "Sharma Traders"
invoices := []Invoice{{"INV-1", &b}, {"INV-2", nil}, {"INV-3", &b}}
for _, inv := range invoices {
safeProcess(inv)
}
}Practice (0/4 done)
What does this print? func main() { defer fmt.Println("bye") panic("oops") }
Where must recover() be called to stop a panic?
A user types a date in the wrong format. What should your parse function do?
Write func safeGet(s []int, i int) (v int, err error) that returns s[i], but converts an out-of-range panic into an error using defer and recover. Then write a second version that avoids panic entirely by checking the index first, and explain in one line which is better.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- any, type assertions and type switchesPointers, interfaces and errors · same module
- Interfaces: behaviour, not family treesPointers, interfaces and errors · same module
- panic! and unrecoverable errorsRust Programming · Error handling and collections
- Middleware: logging, recovery and request IDsBuilding a REST API with a database
- Handling errors wellBackend Development · APIs and request handling
- Go language interview questionsCareers and interviews
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What runs when a function panics?
Answer: Its deferred calls, then the callers' deferred calls
The panic unwinds the stack, running deferred calls as it goes.
Why does regexp.MustCompile panic instead of returning an error?
Answer: It is meant for fixed patterns in your code, where a bad pattern is a programming mistake
Must helpers are for constant inputs; failures indicate bugs.
A goroutine you started panics and has no recover. What happens?
Answer: The whole program crashes
An unrecovered panic in any goroutine terminates the program.
What does net/http do if one of your handlers panics?
Answer: It recovers, logs the panic and closes that connection
The HTTP server recovers handler panics so one request cannot kill the server.
How do you see the chain of calls that led to a panic?Related
Answer: Set RUST_BACKTRACE=1
The backtrace lists the function calls.
Do deferred calls run after os.Exit(1)?Related
Answer: No, os.Exit ends the program immediately
os.Exit skips deferred calls; so does log.Fatal, which calls os.Exit.
Where should panic-recovery middleware go in the chain?Related
Answer: Outermost
Outermost catches panics from all inner middleware and handlers.
What happens if a handler throws and nothing catches it?Related
Answer: The request can hang or the server can crash
Uncaught errors can stop the request or the process. Wrap handlers in try and catch.
From Handling errors well
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Pointers, interfaces and errors”. The module exam comes next.
- Module 4 exam: Pointers, interfaces and errors · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Packages, testing and generics. 46 topics left in the course before the final exam.