Skip to main content

Go Ep 3: Functions, Defer & Closures

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
go - This article is part of a series.
Part 3: This Article
Functions in Go can return multiple values natively. Coupled with the defer statement, Go provides a clean, elegant mechanism for resource management that replaces cumbersome try/finally blocks.

TL;DR (Quick Summary)
#

  • Multiple Return Values: Functions can return (result, error) or multiple values natively without wrapping them in tuple classes.
  • defer Statement: Defers function execution until the surrounding function returns. Executed in LIFO (Last-In, First-Out) order.
  • Variadic Functions: Accept variable numbers of arguments using ...Type (e.g. sum(nums ...int)).
  • Closures: Anonymous functions that capture and bind variables from their surrounding lexical scope.

1. Multiple Return Values & Named Returns
#

In Go, it is idiomatic for functions to return (Value, error).

package main

import (
	"errors"
	"fmt"
)

// Standard Multiple Return Values
func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("cannot divide by zero")
	}
	return a / b, nil
}

// Named Return Values (Pre-declares variables in function signature)
func getDimensions() (width int, height int) {
	width = 1920
	height = 1080
	return // Naked return (returns width and height automatically)
}

func main() {
	res, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Printf("10 / 2 = %.2f\n", res)

	w, h := getDimensions()
	fmt.Printf("Resolution: %dx%d\n", w, h)
}

2. The defer Statement (LIFO Cleanup)
#

The defer statement schedules a function call to run immediately before the enclosing function returns.


graph TD
    subgraph FunctionExecutionTimeline ["Function Execution Timeline"]
        Exec1["Step 1: Open File"] --> Defer1["defer file.Close"]
        Exec2["Step 2: Read Data"] --> Defer2["defer logFinished"]
        Exec3["Step 3: Process Logic"]
        Exec3 -->|Function Return Triggered| LIFOStack["Run Deferred Stack LIFO"]
        LIFOStack --> RunDefer2["Execute: logFinished"]
        RunDefer2 --> RunDefer1["Execute: file.Close"]
    end

Defer Stack Execution Example
#

package main

import "fmt"

func deferDemo() {
	fmt.Println("Start")

	defer fmt.Println("Deferred 1 (Executed Last)")
	defer fmt.Println("Deferred 2 (Executed Second)")
	defer fmt.Println("Deferred 3 (Executed First)")

	fmt.Println("End")
}

func main() {
	deferDemo()
}

Expected Terminal Output:

Start
End
Deferred 3 (Executed First)
Deferred 2 (Executed Second)
Deferred 1 (Executed Last)

Notice that deferred statements run in reverse (stack LIFO) order after "End" is printed!


3. Closures (Anonymous Stateful Functions)
#

A closure is a function value that references variables from outside its body.

package main

import "fmt"

// Returns a function that generates auto-incrementing IDs
func createIDGenerator() func() int {
	id := 0 // Captured state!
	return func() int {
		id++
		return id
	}
}

func main() {
	gen := createIDGenerator()

	fmt.Println("ID:", gen()) // 1
	fmt.Println("ID:", gen()) // 2
	fmt.Println("ID:", gen()) // 3
}

4. Troubleshooting & Common Errors
#

Error 1: Deferring Method Call Argument Evaluation
#

The Cause: defer evaluates arguments at the time the defer statement is encountered, NOT when the deferred function actually executes!

// 🔴 Unexpected behavior: prints 0 because 'i' was 0 when deferred!
i := 0
defer fmt.Println(i)
i = 100

// 🟢 Correct behavior: Wrap in an anonymous function to read updated 'i'
i := 0
defer func() { fmt.Println(i) }() // Prints 100
i = 100

Summary & Next Steps
#

In this episode:

  • We returned multiple values natively from Go functions.
  • We used defer to guarantee resource cleanup in LIFO order.
  • We built stateful anonymous function closures.

In Episode 4: Arrays, Slices & Capacity, we will inspect Go array and slice data structures, memory allocation headers, and append reallocation!

go - This article is part of a series.
Part 3: This Article