0Pricing
Go Academy · Lesson

panic, recover and defer

When to panic and how to recover gracefully

panic, recover and defer is a free Go Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Go Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is panic?

panic stops normal execution of the current goroutine, unwinds the call stack running deferred functions, and crashes the program if not recovered:

package main
import "fmt"

func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("mustPositive: got %d", n))
    }
    return n
}

func main() {
    fmt.Println(mustPositive(5))  // 5
    // fmt.Println(mustPositive(-1)) // panics!
}

When to Use panic

Use panic only for:

  • Truly unrecoverable programmer errors (broken invariants)
  • Impossible conditions that indicate a bug
  • Startup failures (missing config, failed connections during init)

Never use panic for expected failure conditions — use error instead.

defer Basics

defer schedules a function call to run when the surrounding function returns, regardless of how it returns (normal, error, panic):

package main
import "fmt"

func main() {
    defer fmt.Println("third")
    defer fmt.Println("second")  // LIFO order
    defer fmt.Println("first")
    fmt.Println("running...")
    // Output:
    // running...
    // first
    // second
    // third
}

defer for Cleanup

The most common use of defer is ensuring resources are cleaned up:

package main
import ("fmt"; "os")

func readFile(path string) (string, error) {
    f, err := os.Open(path)
    if err != nil {
        return "", fmt.Errorf("open: %w", err)
    }
    defer f.Close()  // always runs, even if we return early

    buf := make([]byte, 1024)
    n, _ := f.Read(buf)
    return string(buf[:n]), nil
}

recover — Catching panics

recover stops a panic and returns the panic value. It must be called directly inside a defer function:

package main
import "fmt"

func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    return a / b, nil  // panics if b==0
}

func main() {
    r, err := safeDiv(10, 0)
    fmt.Println(r, err)
    // 0 recovered from panic: runtime error: integer divide by zero
}

recover Must Be in Deferred Function

recover only works if called directly inside a deferred function. Calling it elsewhere returns nil:

package main
import "fmt"

func wrong() {
    // recover() here does nothing — not in defer
}

func correct() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("caught:", r)
        }
    }()
    panic("oops")
}

func main() { correct() }

Panic in Library Code

Library code should almost never let panics escape to callers. Convert panics to errors at the boundary:

package main
import ("fmt"; "runtime/debug")

func safeCall(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
        }
    }()
    fn()
    return nil
}

func main() {
    err := safeCall(func() { panic("boom") })
    fmt.Println(err != nil) // true
}

defer LIFO Order and Loop Gotcha

Deferred calls run in last-in, first-out (LIFO) order. Be careful deferring inside loops — use a closure or helper function:

package main
import "fmt"

func processFiles(names []string) {
    for _, name := range names {
        name := name  // capture loop variable
        defer func() { fmt.Println("close:", name) }()
    }
}

func main() {
    processFiles([]string{"a.txt", "b.txt", "c.txt"})
    // Output (LIFO): close: c.txt, close: b.txt, close: a.txt
}

defer with Named Return Values

Deferred functions can read and modify named return values — useful for annotating errors:

package main
import "fmt"

func doWork() (err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("doWork: %w", err)
        }
    }()
    return fmt.Errorf("something failed")
}

func main() {
    err := doWork()
    fmt.Println(err) // doWork: something failed
}

panic vs error: Decision Guide

Choosing between panic and error:

  • Use error: expected failures (file not found, network timeout, invalid input)
  • Use panic: programming errors (nil map access, out-of-bounds), impossible states, init failures
  • Never use panic across API boundaries — callers cannot handle it
  • Recover panics at goroutine top-level in servers to avoid crashes

Quick Check

Where must recover() be called to catch a panic?

Recap: panic, recover, defer

Summary:

  • panic: unwinds stack, runs defers, crashes if unrecovered
  • defer: runs on function exit (normal, error, or panic); LIFO order
  • recover: stops panic, returns value; must be in defer
  • Use panic for programmer errors, not expected failures
  • Convert panics to errors at API boundaries

Frequently asked questions

Is the “panic, recover and defer” lesson free?

Yes — the full text of “panic, recover and defer” is free to read here on the web, and the Go Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Go Academy course, upgrade to CoddyKit PRO.

What will I learn in “panic, recover and defer”?

When to panic and how to recover gracefully You practise Go Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Go Academy?

No prior experience is required. Go Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “panic, recover and defer” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Go Academy lesson?

Yes. Every Go Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The error Interface
  2. Creating Custom Errors
  3. Error Wrapping and Unwrapping
  4. panic, recover and defer
← Back to Go Academy