Skip to content
AgileLogics
6 min read

Closures & Function Values

Prerequisites: defer, panic & recover

In Go, functions are values: you can store them in variables, pass them as arguments, and return them from other functions. A function type is written with its signature, such as func(int) int.

A closure is a function literal that refers to variables from the scope around it. The closure keeps those variables alive and shares them — it does not copy them.

Syntax

var op func(int, int) int           // zero value of a function type is nil
op = func(a, b int) int { return a + b }

type Transform func(string) string  // a named function type

func apply(nums []int, f func(int) int) []int { /* ... */ }

Example

package main

import (
    "fmt"
    "strings"
)

func apply(nums []int, f func(int) int) []int {
    out := make([]int, 0, len(nums))
    for _, n := range nums {
        out = append(out, f(n))
    }
    return out
}

// counter returns a closure that remembers count between calls.
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func multiplier(factor int) func(int) int {
    return func(n int) int { return n * factor }
}

func main() {
    double := multiplier(2)
    fmt.Println(apply([]int{1, 2, 3}, double))
    fmt.Println(apply([]int{1, 2, 3}, func(n int) int { return n * n }))

    next := counter()
    fmt.Println(next(), next(), next())

    other := counter() // a separate closure with its own count
    fmt.Println(other())

    shout := strings.ToUpper // named functions are values too
    fmt.Println(shout("go"))

    var op func(int, int) int
    fmt.Println(op == nil)
}

Expected output

[2 4 6]
[1 4 9]
1 2 3
1
GO
true

Since Go 1.22, each iteration of a for loop gets a fresh copy of the loop variable, so closures created inside a loop capture the value from their own iteration. (This applies when your go.mod says go 1.22 or later; older code often has a v := v line to work around the previous behaviour.)

Common mistakes

  • Calling a nil function value — it panics. Check f != nil first if the function is optional.
  • Forgetting that closures share variables: if two closures capture the same variable, a change made through one is visible through the other.
  • Comparing two function values with ==. Functions can only be compared to nil.

Practice

Write makeAccumulator() func(int) int that returns a closure adding each argument to a running total and returning the new total. Call it with 10 and then 5, printing 10 and 15.

Show solution
package main

import "fmt"

func makeAccumulator() func(int) int {
    total := 0
    return func(n int) int {
        total += n
        return total
    }
}

func main() {
    add := makeAccumulator()
    fmt.Println(add(10)) // 10
    fmt.Println(add(5))  // 15
}