Skip to content
Educora
Beginner15 min4 / 10

Functions, closures and defer

Write functions with several results, named results and variadic parameters, and learn closures and `defer`, which saves work for the end of a function.

Check yourself
In this lesson you will learn
  • Write functions that return several and named results
  • Use variadic functions and pass functions as values
  • Explain how a closure captures an outside variable
  • Know the order in which defer runs and when its arguments are evaluated

Every time you press “+” on a calculator, the same addition rule runs. In programming, a reusable piece of code like this is called a function. Go functions can return several results, take other functions as arguments and even “remember” their own state. In this lesson you will also meet defer, the keyword that postpones work until a function finishes.

Declaring functions and results

A function is declared with the func keyword: first the name, then the parameters in parentheses, and finally the result type. Parameters of the same type can be shortened: a, b int instead of a int, b int. What makes Go different is that a function can return several values: (int, int). A result you don't need can be thrown away with the blank identifier _.

Go
package main

import "fmt"

func add(a, b int) int {
	return a + b
}

func divmod(a, b int) (int, int) {
	return a / b, a % b
}

func rectangle(w, h float64) (area, perimeter float64) {
	area = w * h
	perimeter = 2 * (w + h)
	return
}

func main() {
	fmt.Println(add(2, 3))
	q, r := divmod(17, 5)
	fmt.Println(q, r)
	_, rest := divmod(10, 4)
	fmt.Println(rest)
	fmt.Println(rectangle(3, 2.5))
}
Expected output
5
3 2
2
7.5 11

In rectangle the results have names: (area, perimeter float64). Such variables are created with their zero values when the function starts, and a bare return returns their current values. As the last line shows, you can even pass a call to a multi-result function straight into Println.

Variadic functions and functions as values

If you put ... before the type of the last parameter, the function accepts any number of arguments — such a function is called variadic. fmt.Println is one of them. Inside the function this parameter is an ordinary slice. To pass an existing slice, write ... after its name: sum(scores...). In Go functions are also ordinary values: you can store them in variables, pass them to other functions and return them.

Go
package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func apply(n int, f func(int) int) int {
	return f(n)
}

func main() {
	fmt.Println(sum())
	fmt.Println(sum(1, 2, 3))
	scores := []int{90, 85, 70}
	fmt.Println(sum(scores...))
	square := func(x int) int { return x * x }
	fmt.Println(apply(7, square))
}
Expected output
0
6
245
49

Closures

An anonymous function is created right inside the code: func(x int) int { ... }. If it uses a variable from outside, it “captures” that variable and keeps it alive — such a function is called a closure. The counter below creates a new counter on every call: each closure keeps its own count variable.

Go
package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next())
	other := counter()
	fmt.Println(other(), next())
}
Expected output
1 2 3
1 4

next and other are two independent counters: other() starts again from 1, while next() continues its own count and returns 4. The count variable lives on after counter returns, because the closure holds on to it.

Go 1.21 and older: manual copy
var funcs []func()
for i := 0; i < 3; i++ {
	i := i // needed before Go 1.22
	funcs = append(funcs, func() { fmt.Print(i, " ") })
}
Go 1.22+: no copy needed
var funcs []func()
for i := 0; i < 3; i++ {
	funcs = append(funcs, func() { fmt.Print(i, " ") })
}

defer: saving work for the end

The defer keyword postpones a function call until the surrounding function finishes. Whether the function ends normally or exits with an error, deferred calls always run. If there are several defers, they run in reverse order — from the last to the first (LIFO, like a stack of plates). Note: the arguments are evaluated at the moment defer is written, not later.

Go
package main

import "fmt"

func main() {
	fmt.Println("start")
	for i := 1; i <= 3; i++ {
		defer fmt.Println("deferred", i)
	}
	x := 10
	defer fmt.Println("x was", x)
	x = 20
	fmt.Println("end, x =", x)
}
Expected output
start
end, x = 20
x was 10
deferred 3
deferred 2
deferred 1

Look at the output: even though x later became 20, the deferred call printed 10, because the argument was evaluated on the defer line. All deferred calls ran after the end line, in reverse order.

Key points

  • A function is written as func name(params) result; parameters of the same type can be grouped: a, b int.
  • A Go function can return several values; an unneeded result is discarded with _.
  • A ...T parameter accepts any number of arguments; an existing slice is passed as s....
  • A closure captures and keeps an outside variable; since Go 1.22 each loop iteration has its own variable.
  • defer postpones a call to the end of the function, runs deferred calls in reverse order and evaluates arguments immediately.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
Which is a correct call of func divmod(a, b int) (int, int)?