These three keywords handle deferred execution and exceptional situations. defer schedules cleanup, panic triggers a runtime crash (for truly exceptional cases), and recover can catch a panic to prevent the program from crashing.
These three keywords handle deferred execution and exceptional situations. defer schedules cleanup, panic triggers a runtime crash (for truly exceptional cases), and recover can catch a panic to prevent the program from crashing.
func readFile() error {
f, err := os.Open("file.txt")
if err != nil { return err }
defer f.Close() // GUARANTEED to run when readFile returns (any path)
// ... use f, with multiple return points ...
return nil // f.Close() runs here automatically
}
defer schedules a function call to execute when the surrounding function returns — no matter how it returns (normal return, error, or panic). It's the idiomatic way to ensure cleanup: closing files, unlocking mutexes, closing connections — placed right next to the acquisition so you can't forget it.
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// prints: 3, 2, 1 — last deferred, first executed (stack order)
func mustPositive(n int) {
if n < 0 {
panic("negative not allowed") // stops normal execution, unwinds the stack
}
}
// panic runs deferred functions as it unwinds, then crashes the program (with a stack trace)
panic stops normal flow and unwinds the call stack (running deferred functions along the way), ultimately crashing the program. It's for programmer errors / unrecoverable conditions (out-of-bounds, nil dereference, impossible states) — NOT for ordinary errors (those use returned error values).
func safeProcess() {
defer func() {
if r := recover(); r != nil { // recover() returns the panic value
fmt.Println("recovered from:", r) // handle it; program continues
}
}()
panic("something broke") // this panic is caught by the recover above
}
// safeProcess returns normally instead of crashing
recover regains control of a panicking goroutine — but only works inside a deferred function. It's used sparingly, e.g. to stop a single request handler from crashing a whole server.
Normal/expected failures (file missing, bad input, validation) → return an error value
panic/recover → reserve for TRULY exceptional cases (bugs, unrecoverable states)
and boundaries (e.g. a server recovering so one bad request doesn't kill the process)
defer is an essential, idiomatic Go feature for guaranteed cleanup — placing resource release right next to acquisition and ensuring it runs on every return path (including panics), which prevents leaked files, locks, and connections far more reliably than manual cleanup.
Understanding its LIFO ordering is important too. panic/recover provide an exception-like mechanism, but the critical principle is that Go reserves them for genuinely exceptional, unrecoverable situations — normal errors should always be returned as values and checked.
Misusing panic/recover as general exception handling is un-idiomatic and a common mistake for developers coming from exception-based languages.
Knowing when to use defer (always, for cleanup), error returns (normal failures), and panic/recover (rare, exceptional, or at boundaries) is fundamental to writing correct, idiomatic Go and a frequent interview topic.