Why log.Fatalf skips deferred functions in Go

2026-10-04

Using log.Fatalf in main can prevent deferred functions from running.

log.Fatalf logs the message and then calls os.Exit(1). Unlike a normal return, os.Exit terminates the process immediately, so Go does not unwind the stack and pending defer statements are not executed.

For example:

package main

import (
    "fmt"
    "log"
)

func main() {
    defer fmt.Println("cleanup executed")

    fmt.Println("starting")
    log.Fatalf("something went wrong")
}

Go playground example.

Output

starting
something went wrong

The “cleanup executed” message is never printed because log.Fatalf terminates the process before the deferred function can run.

A better pattern is to move the application logic into a function that returns an error:

package main

import (
    "errors"
    "fmt"
    "log"
)

func main() {
    if err := run(); err != nil {
        log.Fatalf("application failed: %v", err)
    }
}

func run() error {
    defer fmt.Println("cleanup executed")

    fmt.Println("starting")

    return errors.New("something went wrong")
}

Go playground example

Output

starting
cleanup executed
2009/11/10 23:00:00 application failed: something went wrong

Because there is no log.Fatal or os.Exit, the functions return normally and Go executes their deferred functions during stack unwinding.

In general, application code should return errors rather than terminate the process directly. This makes cleanup with defer reliable and also makes the code easier to test.