Go defer keyword: LIFO cleanup, evaluation rules, and loops

Use golang defer to schedule a function call when the surrounding function returns; arguments are evaluated immediately; multiple defers run in LIFO order; avoid defer in tight loops without an inner function.

Published

Updated

Read time 3 min read

Reviewed byDeepak Prasad

Go defer keyword: LIFO cleanup, evaluation rules, and loops

The golang defer statement schedules a function call to run when the surrounding function exits—after return, falling off the end, or during panic unwind. Arguments are evaluated immediately; only the call is deferred. Multiple defers run in last-in, first-out order. That keeps cleanup next to setup while working naturally with functions and methods.

Tested with Go 1.24 on Linux.


Basic defer and evaluation order

go
package main

import "fmt"

func main() {
	defer fmt.Println("Hello World")
	fmt.Println("Introduction to defer statement in Go")
}
Output

You should see the introduction line first, then Hello World.


Stacking defers (LIFO)

go
package main

import "fmt"

func main() {
	defer fmt.Println("FOUR")
	defer fmt.Println("THREE")
	defer fmt.Println("TWO")
	defer fmt.Println("ONE")
}
Output

You should see ONE through FOUR counting up.


Defer with methods

go
package main

import (
	"fmt"
	"math"
)

type circle struct {
	radius float64
}

func (c circle) area() {
	fmt.Printf("The area is : %f \n", math.Pi*c.radius*c.radius)
}
func (c circle) circumference() {
	fmt.Printf("The circumference is %f \n", math.Pi*(c.radius*2))
}
func show() {
	fmt.Println("Calculating the area and circumference of a circle")
}
func main() {
	c := circle{radius: 7}
	defer c.area()
	defer c.circumference()
	defer show()
}
Output

You should see show text first, then circumference, then area (LIFO among the three defers).


Defer inside a loop

Defer runs when the outer function returns, not each iteration. Accumulating defer file.Close() in a loop can hold many descriptors open until the loop’s function finishes. Fix it by moving per-iteration work into a nested function so each defer pairs with one iteration return:

go
package main

import "os"

func readFiles(ch <-chan string) error {
	for path := range ch {
		if err := readFile(path); err != nil {
			return err
		}
	}
	return nil
}

func readFile(path string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	return nil
}

Close a file after creating it

go
package main

import (
	"fmt"
	"log"
	"os"
)

func createFile(filename string) {
	file, err := os.Create(filename)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("File with name %s created successfully \n", filename)
	defer file.Close()
	defer fmt.Printf("Check working directory for %s \n", filename)
}

func main() {
	createFile("sample.txt")
}

Run locally; the deferred prints and close run when createFile returns.


Close an HTTP response body

go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	res, err := http.Get("https://example.com")
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	n, err := io.Copy(io.Discard, res.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("bytes read:", n)
}
Output

You should see a positive byte count; always defer res.Body.Close() after a successful Get so connections can be reused.


Defer with WaitGroup

go
package main

import (
	"fmt"
	"sync"
	"time"
)

func myRoutine(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 3; i++ {
		fmt.Println(i)
		time.Sleep(10 * time.Millisecond)
	}
}

func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	go myRoutine(&wg)
	go myRoutine(&wg)
	wg.Wait()
}
Output

You should see interleaved 0, 0, then 1, 1, and so on; defer wg.Done() runs when each goroutine exits (WaitGroup, goroutines, for loops).


Summary

The golang defer keyword keeps cleanup next to setup: deferred calls run in LIFO order when the function returns, after their arguments were already computed. Use it for files, HTTP bodies, locks, and WaitGroup.Done, but not bare inside long loops—add a per-iteration function boundary instead.


References


Frequently Asked Questions

1. When does a deferred function run?

After the surrounding function returns, whether by reaching the end, an explicit return, or a panic unwinding that frame.

2. Are defer arguments evaluated later?

No. The function value and arguments are evaluated when the defer statement executes; only the invocation is delayed.

3. In what order do multiple defers run?

Last deferred runs first (LIFO stack).

4. Why is defer inside a loop risky?

All defers wait until the outer function returns, which can delay closes and leak resources; wrap each iteration in a function so defer runs per iteration.

5. What is defer used for most often?

Closing files, HTTP response bodies, unlocking mutexes, finishing WaitGroup counters, and similar cleanup paired with setup.
Antony Shikubu

Systems Integration Engineer

Highly skilled software developer with expertise in Python, Golang, and AWS cloud services.