Closures

A function can reference variables declared in the scope around it, not just its own parameters and locals. When you pass that function out of its birthplace (by assigning it to a variable, returning it, or passing it as an argument), Go keeps those referenced variables alive for as long as the function exists. A function that captures variables from its surrounding scope is called a closure.

The classic counter

Here is the canonical example. counter returns a function that, each time you call it, produces the next integer:

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

func main() {
    next := counter()

    fmt.Println(next())   // 1
    fmt.Println(next())   // 2
    fmt.Println(next())   // 3
}

counter declares a local n initialised to zero and returns an anonymous function that increments and returns it. Normally n would disappear the moment counter returned (it is a function-local variable), but because the returned function still refers to n, Go keeps it alive. Every call to next() sees and updates the same n.

That is the essence of a closure: the returned function "closes over" n, carrying it along for the rest of its life.

What is a closure

A closure is a function value that keeps access to variables from the surrounding scope, even after that outer function has returned.

Each closure has its own captured state

Call counter() more than once and you get independent counters. Each call runs the body afresh, so each gets its own n:

first := counter()
second := counter()

fmt.Println(first())    // 1
fmt.Println(first())    // 2
fmt.Println(second())   // 1  (independent)
fmt.Println(second())   // 2

first and second each close over their own n. Incrementing one does not touch the other. This is what makes closures useful as tiny state machines: you create as many independent pieces of state as you need, just by calling the factory more times.

When closures are the right tool

Closures shine when you have a small piece of context that belongs to one helper and you do not want to turn it into a whole top-level function. A local helper can read values from the surrounding scope and stay private to the function that uses it.

func greetPair(a, b string) {
    prefix := "Hello"

    greet := func(name string) {
        fmt.Println(prefix + ", " + name)
    }

    greet(a)
    greet(b)
}

greet captures prefix from the enclosing function. That makes the helper easy to read and keeps it local to the one place that needs it. There is no need for a separate top-level greetWithPrefix(prefix, name) helper if the prefix only matters inside this one function.

When the captured state grows complex or is shared across many methods, reach for a struct with methods (covered in the Structs and Methods chapters) instead. Closures are at their best when the captured context is small and belongs to exactly one function.

Closures capture by reference, not by value

The trickiest thing about closures is what they capture. A closure does not snapshot the current value of an outer variable; it captures the variable itself. Two closures that refer to the same outer variable see the same storage, and changes through one are visible through the other.

Watch the effect on a single variable n captured by two closures:

n := 1

inc := func()      { n++ }
show := func() int { return n }

fmt.Println(show())   // 1
inc()
fmt.Println(show())   // 2  (inc changed n, show sees the new value)
inc()
inc()
fmt.Println(show())   // 4

show never touches n directly, yet its result keeps changing. Both closures point at the same n, and updates through inc are visible to show on the next call. If closures captured by value, show would have stayed frozen at 1.

This shared-storage rule is what makes the counter factory above work: the returned function sees the same n on every call.

Note

If you build closures inside loops or goroutines, keep this rule in mind: closures capture variables, not frozen values. Whenever several closures share one outer variable, they all see updates to that same variable. The Concurrency chapter comes back to this in a setting where it matters more.

Task
  • Write a factory function called multiplier that takes one int parameter, factor, and returns a func(int) int.

  • The returned function should take an int and return that int multiplied by factor.

  • Then in main, create two multipliers and use them:

  • Call multiplier(2) and assign the result to double. Print double(5), which should be 10.

  • Call multiplier(10) and assign the result to tenTimes. Print tenTimes(7), which should be 70.

Each multiplier closes over its own factor, so they do not interfere.

Expected output
10
70