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
package main
import "fmt"
func main() {
defer fmt.Println("Hello World")
fmt.Println("Introduction to defer statement in Go")
}You should see the introduction line first, then Hello World.
Stacking defers (LIFO)
package main
import "fmt"
func main() {
defer fmt.Println("FOUR")
defer fmt.Println("THREE")
defer fmt.Println("TWO")
defer fmt.Println("ONE")
}You should see ONE through FOUR counting up.
Defer with methods
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()
}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:
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
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
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)
}You should see a positive byte count; always defer res.Body.Close() after a successful Get so connections can be reused.
Defer with WaitGroup
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()
}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.

