Day 10 - defer, panic, and recover
Error Handling in Go — defer, panic, and recover
In Go, we use defer, panic, and recover statements to handle errors effectively.
🔹 Overview
- Defer: This statement delays the execution of a function until the surrounding function completes.
- Panic: Immediately stops the normal flow of the program when an unrecoverable error occurs.
- Recover: Regains control after a panic to prevent the program from crashing.
Before reading this, check my previous blog — Error Handling in Go.
🧩 Defer in Go
defer schedules a function call to be executed after the surrounding function finishes, no matter how it exits (success, return, or panic).
🧠 Common Uses
- Closing files, DBs, or network connections
- Unlocking mutexes
- Releasing resources
Notes:
- Defer follows the LIFO (Last In, First Out) model.
- Deferred functions execute after all other code in the function completes.
🧮 Example — Simple
package main
import "fmt"
func main() {
// Defer
defer fmt.Println("one")
defer fmt.Println("two") // LIFO model: executes this line first, then the above line
fmt.Println("three")
}
Output:
three
two
one
🧩 Real-Time Scenarios
Scenario 1: Database Connection Cleanup
When you open a database connection or file, you must always close it after use.
If you forget, you may leak resources or lock files.
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq" // PostgreSQL driver
)
func main() {
// Open database connection
db, err := sql.Open("postgres", "user=postgres password=1234 dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
// Close connection when function exits
defer db.Close()
// Execute a query
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatal(err)
}
// Close rows after processing
defer rows.Close()
for rows.Next() {
var id int
var name string
rows.Scan(&id, &name)
fmt.Println(id, name)
}
}
Scenario 2: File Handling (Always Close the File)
When reading or writing files, you should always close them once done.
Using defer ensures the file is properly closed, even if an error occurs midway.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("example.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
// Ensure file is closed when function exits
defer file.Close()
// Write some content
_, err = file.WriteString("Hello, Go Developer!")
if err != nil {
fmt.Println("Error writing to file:", err)
return
}
fmt.Println("File written successfully!")
// Even if an error occurs before the function ends,
// defer ensures the file is closed properly.
}
We also use defer with panic and recover, discussed below.
How Other Languages Handle This
Python
Python uses context managers (with blocks), which automatically call __enter__ at the start and __exit__ when the block ends — similar to Go’s defer.
with open("example.txt", "w") as f:
f.write("Hello, Python Developer!")
Java — Try-with-Resources
In Java, the try-with-resources statement ensures that any object implementing AutoCloseable is automatically closed at the end of the block.
import java.io.*;
public class Example {
public static void main(String[] args) {
try (FileWriter writer = new FileWriter("example.txt")) {
writer.write("Hello, Java Developer!");
} catch (IOException e) {
e.printStackTrace();
}
// writer.close() is automatically called
}
}
JavaScript / TypeScript
JavaScript doesn’t have built-in defer, but you can simulate it using a try...finally block.
function writeFile() {
const file = openFile("example.txt"); // hypothetical
try {
file.write("Hello, JS Developer!");
} finally {
file.close(); // runs even if an error occurs
}
}
💥 Panic in Go
The panic statement is used when something goes terribly wrong — an unrecoverable error occurs that makes it impossible for the program to continue.
🔹 When Panic Happens
- The normal flow of execution stops.
- All deferred functions are executed (in reverse order).
- The program crashes and prints the error message + stack trace.
🧮 Example
package main
import "fmt"
func main() {
fmt.Println("line1")
panic("Ending the program")
fmt.Println("line3") // This line will not execute
}
Real-Time Scenario: Invalid Configuration or Missing .env Variable
If your application requires an environment variable (like a database URL) and it’s missing, you should panic.
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("Starting Application...")
dbURL := os.Getenv("DB_URL")
if dbURL == "" {
panic("❌ Missing required environment variable: DB_URL")
}
fmt.Println("Connected to Database:", dbURL)
fmt.Println("Application running successfully!")
}
If you forget to set DB_URL, the panic statement executes, prints the message, and terminates the program.
🧠 Best Practices for Panic
| ✅ Do | ❌ Avoid |
| Use for truly unrecoverable errors | Don’t use panic for simple validation errors |
| Log detailed error info before panicking | Don’t panic in library code (let the caller handle it) |
| Always defer cleanup if panic may occur | Don’t panic without a clear message |
🧩 Recover in Go
recover() is a built-in function that allows a program to regain control after a panic occurs.
It’s used inside a deferred function to stop the panic and prevent the program from crashing.
⚙️ Behavior
- If called inside a deferred function and a panic is happening →
recover()catches it. - If called when there’s no panic → it returns
nil.
🔹 Example
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
🧩 Real-Time Scenario 1: Recover from Panic to Keep the Server Running
Imagine a web server that must not crash if one request panics.
package main
import "fmt"
func handleRequest(id int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("❌ Recovered from panic in request:", id, "=>", r)
}
}()
fmt.Println("Processing request:", id)
if id == 3 {
panic("Something went wrong while processing request!") // simulate crash
}
fmt.Println("Request", id, "processed successfully")
}
func main() {
fmt.Println("Starting server...")
for i := 1; i <= 5; i++ {
handleRequest(i)
}
fmt.Println("Server still running after handling all requests!")
}
Output:
Starting server...
Processing request: 1
Request 1 processed successfully
Processing request: 2
Request 2 processed successfully
Processing request: 3
❌ Recovered from panic in request: 3 => Something went wrong while processing request!
Processing request: 4
Request 4 processed successfully
Processing request: 5
Request 5 processed successfully
Server still running after handling all requests!
If we remove the recover() function, the program would crash as shown below:
Starting server...
Processing request: 1
Request 1 processed successfully
Processing request: 2
Request 2 processed successfully
Processing request: 3
panic: Something went wrong while processing request!
goroutine 1 [running]:
main.handleRequest(0x3)
D:/Go Tutorial/programs/Day10/deferPanicRecover.go:15 +0x154
main.main()
D:/Go Tutorial/programs/Day10/deferPanicRecover.go:25 +0x5b
exit status 2
🧩 Real-Time Scenario 2: Safely Handle Function Errors in a Program
Let’s handle a panic gracefully in a divide function, so the program continues without crashing.
package main
import "fmt"
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
result = 0 // default safe value
}
}()
if b == 0 {
panic("Division by zero not allowed!")
}
return a / b
}
func main() {
fmt.Println("Starting calculation...")
fmt.Println("Result 1:", safeDivide(10, 2))
fmt.Println("Result 2:", safeDivide(10, 0)) // triggers panic but recovered
fmt.Println("Result 3:", safeDivide(15, 3))
fmt.Println("Program completed without crashing")
}
Output:
Starting calculation...
Result 1: 5
Recovered from panic: Division by zero not allowed!
Result 2: 0
Result 3: 5
Program completed without crashing
Real-World Use Cases for Recover
| Scenario | Example |
| Web servers | Prevent one crashing request from stopping the entire server |
| Background jobs | Recover from one failed task and continue others |
| Library code | Prevent internal panics from crashing user applications |
| Graceful shutdown | Log and clean up before terminating the program |
FAQs — Panic, Recover, and Error Handling in Go
1. How do other languages (Python & Java) handle panic and recover?
In Go, panic and recover work together like a low-level exception system, but Go encourages explicit error handling with error values rather than exceptions.
Other languages, however, rely on exception handling mechanisms like try-catch.
🟢 Python
Python uses the try / except block for exception handling.
try:
print("Before error")
raise Exception("Something went wrong!")
except Exception as e:
print("Recovered:", e)
Java
Java uses try / catch blocks to handle exceptions.
public class Example {
public static void main(String[] args) {
try {
System.out.println("Before error");
throw new Exception("Something went wrong!");
} catch (Exception e) {
System.out.println("Recovered: " + e.getMessage());
}
}
}
Key difference:
In Go, panics are not the main way to handle normal errors (unlike exceptions in Python/Java). Go expects you to handle most errors explicitly by returning error values, not by using panic.
2. What is the difference between custom errors and panic in Go?
| Aspect | Custom Errors | Panic |
| Purpose | Handle expected errors that may occur during normal operation | Handle unexpected, unrecoverable conditions |
| How to create | errors.New("message") or fmt.Errorf("format") | panic("message") |
| Recoverable? | Yes — handled by checking if err != nil | Only recoverable via recover() |
| Example use case | File not found, invalid input, DB connection failed | Array out of bounds, nil pointer dereference, logic error |
| Control flow | Program continues normally after error handling | Normal execution stops immediately unless recovered |
Example — Custom Error:
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Handled error:", err)
return
}
fmt.Println("Result:", result)
}
3. When should you use panic and recover?
| Situation | Should you use panic? |
| Recoverable issues (like invalid user input, failed API calls) | ❌ No — use custom error |
| Unrecoverable errors (like corrupted state, missing config) | Yes, panic is justified |
| Library code returning error to caller | ❌ Never panic, always return error |
Top-level code (like main or goroutine handlers) | Can use recover() to prevent crash |
Rule of thumb:
Use errors for expected problems.
Use panic/recover for truly exceptional situations.
4. Is try-catch the same as panic-recover?
Not exactly.
In Java/Python, exceptions are integrated into the language — all errors are raised and caught using try-catch.
In Go, panics are rarely used, and most code uses explicit error values to keep control flow simple and predictable.
| Concept | Go | Python / Java |
| Common Error Handling | Return error values | Raise exceptions |
| Critical Failures | Use panic | Throw exception |
| Recovery Mechanism | recover() inside defer | try-catch block |