Skip to main content
Go supports anonymous functions, which can form closures. Anonymous functions are useful when you want to define a function inline without having to name it. Closures allow these functions to access and manipulate variables from their enclosing scope.

What is a Closure?

A closure is a function value that references variables from outside its body. The function can access and modify these variables, and the variables persist between function calls.
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}
This function intSeq returns another function (defined anonymously). The returned function closes over the variable i, forming a closure.
The variable i is defined in intSeq but persists across multiple calls to the returned function. Each closure maintains its own independent state.

Using Closures

When you call a function that returns a closure, the closure captures its own copy of the variables:
nextInt := intSeq()

fmt.Println(nextInt())  // Output: 1
fmt.Println(nextInt())  // Output: 2
fmt.Println(nextInt())  // Output: 3
Each call to nextInt() increments and returns its own i variable.

Independent State

Each closure maintains independent state. Creating a new closure creates a new set of variables:
newInts := intSeq()
fmt.Println(newInts())  // Output: 1 (starts fresh)

Complete Example

package main

import "fmt"

// Returns a closure that increments and returns a counter
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

func main() {
    // Create a closure with its own state
    nextInt := intSeq()

    // Call the closure multiple times
    fmt.Println(nextInt())  // 1
    fmt.Println(nextInt())  // 2
    fmt.Println(nextInt())  // 3

    // Create a new closure with independent state
    newInts := intSeq()
    fmt.Println(newInts())  // 1
}

Practical Use Cases

Iterators

func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        result := a
        a, b = b, a+b
        return result
    }
}

fib := fibonacci()
fmt.Println(fib())  // 0
fmt.Println(fib())  // 1
fmt.Println(fib())  // 1
fmt.Println(fib())  // 2

Configuration

func makeMultiplier(factor int) func(int) int {
    return func(x int) {
        return x * factor
    }
}

double := makeMultiplier(2)
triple := makeMultiplier(3)

fmt.Println(double(5))  // 10
fmt.Println(triple(5))  // 15

Inline Anonymous Functions

You can also use anonymous functions inline without creating closures:
func main() {
    // Define and immediately call an anonymous function
    result := func(x, y int) int {
        return x + y
    }(3, 4)
    
    fmt.Println(result)  // Output: 7
}

Closures with Goroutines

Closures are commonly used with goroutines, but be careful about variable capture:
// Incorrect: all goroutines share the same variable
for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i)  // May print unexpected values
    }()
}

// Correct: pass the variable as a parameter
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n)  // Prints 0, 1, 2, 3, 4
    }(i)
}
When using closures with loops and goroutines, be aware that all iterations might share the same variable unless you explicitly pass it as a parameter or create a new variable in each iteration.

Advanced Example: Memoization

Closures are perfect for implementing memoization:
func memoize(fn func(int) int) func(int) int {
    cache := make(map[int]int)
    return func(n int) int {
        if result, found := cache[n]; found {
            return result
        }
        result := fn(n)
        cache[n] = result
        return result
    }
}

// Expensive computation
func fibonacci(n int) int {
    if n < 2 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
    memoFib := memoize(fibonacci)
    fmt.Println(memoFib(40))  // Fast after first call
}

Best Practices

Closures provide a way to create private state without defining structs:
func newCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}
The count variable is inaccessible from outside, providing true encapsulation.
Variables captured by closures can lead to unexpected behavior, especially in loops. Always verify what the closure captures.
Closures keep their captured variables alive, which can prevent garbage collection. For long-lived closures, be mindful of what they capture.
Closures are ideal for callbacks that need to maintain state:
func setupHandler(prefix string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "%s: %s", prefix, r.URL.Path)
    }
}

Key Takeaways

  • Closures are anonymous functions that capture variables from their enclosing scope
  • Each closure maintains independent state
  • Variables captured by closures persist between function calls
  • Closures are useful for iterators, configuration, and encapsulation
  • Be careful with variable capture in loops and goroutines

Build docs developers (and LLMs) love