← Back to Go Course | Chapter 9: Error Handling | Lesson 4 of 6

panic and recover

panic is Go slamming the emergency brake when something goes badly wrong, and recover is a special catch that can stop the crash and let the program keep going.

Triggering a panic

Calling panic(value) immediately stops the current function's normal execution and begins unwinding the stack, running any deferred calls along the way, until either something recovers or the program crashes.

Example: Triggering a panic

markup
package main

import "fmt"

func mustBePositive(n int) int {
	if n < 0 {
		panic("negative number not allowed")
	}
	return n
}

func main() {
	defer fmt.Println("this still runs during unwind")
	fmt.Println(mustBePositive(5))
}

Recovering from a panic

recover, called from within a deferred function, stops the panic from propagating further and returns the value passed to panic -- allowing the program to log the problem and keep running instead of crashing entirely.

Note: recover() only has an effect when called directly inside a deferred function.

Example: Recovering from a panic

markup
package main

import "fmt"

func safeDivide(a, b int) (result int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("recovered from:", r)
			result = 0
		}
	}()
	return a / b
}

func main() {
	fmt.Println(safeDivide(10, 0))
	fmt.Println("program continues normally")
}

panic vs Returning an Error

Idiomatic Go reserves panic for programmer errors or truly unrecoverable situations (like a required invariant being violated), while ordinary, expected failure conditions -- a missing file, invalid input -- should be communicated through a returned error instead.

Note: If you find yourself panicking for something a caller might reasonably want to handle, return an error instead.

Example: panic vs Returning an Error

markup
package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero") // expected failure: use error
	}
	return a / b, nil
}

func main() {
	result, err := divide(10, 0)
	if err != nil {
		fmt.Println("handled gracefully:", err)
		return
	}
	fmt.Println(result)
}
Common Mistakes
  1. Using panic for ordinary, expected error conditions instead of returning an error -- panic is reserved for truly exceptional situations.
  2. Calling recover() outside of a deferred function, where it has no effect and always returns nil.
  3. Forgetting that recover only stops the panic in the goroutine where it's called -- it can't recover a panic happening in a different goroutine.
Chapter Summary
  • panic immediately stops normal execution and starts unwinding the call stack, running deferred calls along the way.
  • recover, called inside a deferred function, stops a panic in progress and lets the program continue.
  • panic/recover is reserved for truly exceptional situations -- not a substitute for normal error returns.
  • An unrecovered panic crashes the whole program with a stack trace.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.