Golang os exit, defer, and why deferred functions are not run

Golang os exit defer: os.Exit golang behavior matches pkg.go.dev os Exit — program terminates immediately and deferred functions are not run; patterns to run cleanup before exit, go os.exit deferred functions official docs, and alternatives to os.Exit in main.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Golang os exit, defer, and why deferred functions are not run

Searches such as pkg.go.dev os exit deferred functions are not run, go os.exit deferred functions are not run official docs, golang os exit defer, os exit golang, golang os.exit, and pkg.go.dev os exit program terminates immediately deferred functions are not run all describe the same rule: os.Exit ends the process without unwinding the current goroutine’s deferred stack the way a return does. This page states that rule, fixes a broken sample from older drafts, and shows small patterns people use when they still need an exit code after defer cleanup.

Tested with Go 1.24 on Linux.


Why deferred functions are not run after os.Exit

The Go documentation for os.Exit says the program terminates immediately and that deferred functions are not run. That matches queries like go os.exit deferred functions are not run official documentation: os.Exit does not return from main; it asks the runtime to stop the whole program, so defers queued in main never execute.

Abrupt termination from signals or log.Fatal (which calls os.Exit internally) behaves the same from the defer perspective: there is no graceful unwind of your main defers unless you install a signal handler that performs cleanup itself.

This minimal program never prints the deferred line because os.Exit runs before main returns:

go
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	defer func() {
		fmt.Println("This is a defer function")
	}()

	time.Sleep(100 * time.Millisecond)
	os.Exit(1)
}

Running it ends with exit status 1 and no stdout from the defer.


Patterns when you need cleanup and an exit code

Run os.Exit after another function’s defers execute

Call a helper that registers defers, does work, returns an exit code, and pass that code to os.Exit in main. Defers inside the helper still run, because that function returns before os.Exit stops the process.

go
package main

import (
	"fmt"
	"os"
)

func run() int {
	defer func() { fmt.Println("This is the defer function!") }()
	return 1
}

func main() {
	fmt.Println("This is the main function!")
	os.Exit(run())
}

Running it prints both fmt lines, then exits with status 1.

Defer os.Exit so earlier defers run first

Defer order is last-in, first-out. Register defer os.Exit(code) first, then other defers; when main returns, outer defers run, and the deferred os.Exit runs last. Keep the exit code in a variable the inner defers may update.

go
package main

import (
	"fmt"
	"os"
)

func main() {
	exitCode := 0
	defer func() { os.Exit(exitCode) }()
	defer func() { fmt.Println("This is defer function no 1") }()
	defer func() { fmt.Println("This is defer function no 2") }()

	for testNum := 1; testNum < 100; testNum++ {
		fmt.Println("Test number:", testNum)
		if testNum > 3 {
			exitCode = 1
			return
		}
	}
}

This prints the loop lines, then the two defer messages, then exits with code 1.

runtime.Goexit in main (niche)

runtime.Goexit stops the current goroutine after running its defers. If the main goroutine calls Goexit and no other goroutine is running, the program ends. If other goroutines are still running, the program keeps going—easy to misuse. A pattern some codebases use is to defer os.Exit before calling Goexit from main so defers run, then the process exits with the code passed to os.Exit:

go
package main

import (
	"fmt"
	"os"
	"runtime"
	"time"
)

func main() {
	defer os.Exit(1)
	defer func() { fmt.Println("This is the defer function") }()

	fmt.Println("This is the main function")
	time.Sleep(100 * time.Millisecond)
	runtime.Goexit()
}

Running it prints the main line, the custom defer line, then terminates with status 1. Prefer the helper-return or deferred-os.Exit patterns above unless you already rely on Goexit for goroutine control.

Controlled panic and recover

For library-style “early exit with code” without threading os.Exit everywhere, some projects panic a small sentinel type and let a top-level defer recover and call os.Exit—still a panic, so it is not a general control-flow tool.

go
package main

import (
	"fmt"
	"os"
)

type Exit struct{ Code int }

func exitHandler() {
	fmt.Println("This is the defer function")
	if e := recover(); e != nil {
		if exit, ok := e.(Exit); ok {
			os.Exit(exit.Code)
		}
		panic(e)
	}
}

func main() {
	defer exitHandler()
	defer fmt.Println("This is the main function")
	panic(Exit{2})
}

This prints both messages and exits with status 2 without dumping an unhandled panic trace for the Exit sentinel (your handler consumes it).


Summary

pkg.go.dev os exit deferred functions are not run because os.Exit ends the process immediately—golang os exit defer questions are answered by that single rule. Practical os exit golang code either moves os.Exit into a helper that returns after its own defers, defers os.Exit last so other defers finish, uses runtime.Goexit only with care, or funnels through panic/recover when a framework pattern already exists. For normal programs, prefer letting main finish naturally after cleanup, or call os.Exit once from a small top-level block after defers in helpers have run, instead of scattering exits through deep call stacks.


References


Frequently Asked Questions

1. Does pkg.go.dev say os Exit deferred functions are not run?

Yes: os.Exit stops the process immediately; defers registered in main are not executed because main does not return normally.

2. What is the difference between golang os.exit and returning from main?

The standard library identifier is os.Exit (capital E). Returning from main runs deferred calls on the way out; os.Exit bypasses that unwind.

3. How can I honor defers and still set a non-zero exit code?

Run cleanup in defers, then call os.Exit from a helper after those defers fire, defer os.Exit as the first defer so it runs last on return, use runtime.Goexit only with a clear concurrency story, or use a controlled panic type plus recover before os.Exit.

4. Is defer os.Exit safe inside main?

Deferring os.Exit is legal and often used so other defers run first, but the exit code expression is fixed when the defer is registered unless you pass a pointer or closure variable you update earlier.

5. Where is the official documentation for go os.exit deferred functions are not run?

The os package documentation for Exit on pkg.go.dev states that the program terminates immediately and deferred functions are not run.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …