Function Values and Closures

In Go, functions are values: you can write them inline without a name, store them in variables, maps and structs, pass them to other functions and return them. An inline function that uses variables from the code around it is a closure — it keeps those variables alive and can change them. This lesson covers anonymous functions, function types, closures and the patterns built on them.

Anonymous functions

A function literal is written like a declaration without a name. You can assign it, pass it, or call it immediately:

GO
package main

import (
	"fmt"
	"slices"
	"strings"
)

func main() {
	shout := func(s string) string {
		return strings.ToUpper(s) + "!"
	}
	fmt.Println(shout("hello"))

	words := []string{"kiwi", "fig", "banana"}
	slices.SortFunc(words, func(a, b string) int { return len(a) - len(b) })
	fmt.Println(words)

	func() {
		fmt.Println("called straight away")
	}()
}
Output
HELLO!
[fig kiwi banana]
called straight away

Passing a small function literal to another function — like the comparison for SortFunc — is the most common use.

Function types

Give a function signature a name with type to make code that passes functions around easier to read:

GO
package main

import "fmt"

type Operation func(a, b float64) float64

var ops = map[string]Operation{
	"+": func(a, b float64) float64 { return a + b },
	"-": func(a, b float64) float64 { return a - b },
	"*": func(a, b float64) float64 { return a * b },
	"/": func(a, b float64) float64 { return a / b },
}

func main() {
	for _, sym := range []string{"+", "*", "/", "%"} {
		op, ok := ops[sym]
		if !ok {
			fmt.Println(sym, "not supported")
			continue
		}
		fmt.Println(9, sym, 4, "=", op(9, 4))
	}

	var nothing Operation
	fmt.Println(nothing == nil) // the zero value of a function type is nil
}
Output
9 + 4 = 13
9 * 4 = 36
9 / 4 = 2.25
% not supported
true

Calling a nil function panics, so check before calling one that might not be set. Function values can be compared only with nil.

Closures

A function literal can use variables declared outside it. It doesn't copy them — it shares them, and keeps them alive for as long as the function itself exists:

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() // a separate count variable
	fmt.Println(other(), next())
}
Output
1 2 3
1 4

Each call to counter creates a new count variable and a function bound to it. The variable outlives counter's return because the closure still refers to it.

Closures that capture loop variables

Closures see the variable, not a snapshot of its value. Since Go 1.22, each loop iteration has its own variable, so closures created in a loop behave as you'd expect:

GO
package main

import "fmt"

func main() {
	var actions []func()
	for _, name := range []string{"ana", "ben", "cleo"} {
		actions = append(actions, func() { fmt.Println("hello,", name) })
	}
	for _, act := range actions {
		act()
	}

	total := 0
	add := func(n int) { total += n } // modifies the outer variable
	add(5)
	add(10)
	fmt.Println("total:", total)
}
Output
hello, ana
hello, ben
hello, cleo
total: 15

Functions that build functions

Returning a closure lets you create specialised functions from general ones:

GO
package main

import "fmt"

func multiplier(factor float64) func(float64) float64 {
	return func(x float64) float64 { return x * factor }
}

func withPrefix(prefix string) func(string) string {
	return func(s string) string { return prefix + s }
}

func main() {
	double := multiplier(2)
	addVAT := multiplier(1.2)
	fmt.Println(double(21), addVAT(50))

	warn := withPrefix("WARNING: ")
	fmt.Println(warn("disk almost full"))
}
Output
42 60
WARNING: disk almost full

Wrapping functions

A function that takes a function and returns a new one with extra behaviour is called a decorator or middleware. HTTP servers use this pattern heavily (lesson 61):

GO
package main

import (
	"fmt"
	"strings"
)

type Handler func(string) string

func logged(name string, h Handler) Handler {
	return func(in string) string {
		out := h(in)
		fmt.Printf("%s(%q) = %q\n", name, in, out)
		return out
	}
}

func main() {
	upper := logged("upper", strings.ToUpper)
	trim := logged("trim", strings.TrimSpace)
	upper(trim("  go gopher  "))
}
Output
trim("  go gopher  ") = "go gopher"
upper("go gopher") = "GO GOPHER"

Notice that strings.ToUpper can be passed where a Handler is expected: its signature, func(string) string, matches the type.

Method values

A method bound to a particular value can also be used as a function value (methods are covered in lesson 38):

GO
package main

import (
	"fmt"
	"strings"
)

func main() {
	var b strings.Builder
	write := b.WriteString // bound to b
	write("method ")
	write("values")
	fmt.Println(b.String())
}
Output
method values

Try it yourself

Write memoize, which wraps a slow function with a cache stored in a closure. Count how many times the real function runs.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
16 25 16 16 25 36
real calls: 3
Next: cleaning up with defer.