>_
GolangStepByStep
Intern

Closures

Functions that capture variables from their surrounding scope

# Why Closures Matter

A closure is a function value that captures variables from the surrounding scope. It lets you keep private state without creating a struct type.

Closures power real production patterns: middleware, retry wrappers, memoization, functional options, and callback-based data pipelines.

Real-World Analogyclick to expand

Think of a closure like a worker carrying a clipboard. The function body is the worker, and captured variables are the notes on that clipboard.

Every time the worker runs, it reads/writes the same clipboard state, which is why counters, caches, and wrappers work naturally.

# Stateful Closure Example

The classic counter shows closure state persistence across calls.

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

c := makeCounter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3

c2 := makeCounter()
fmt.Println(c2()) // 1 (independent state)

# Step-by-Step Capture Semantics

Closures capture variables, not snapshots

If outer code and closure both access x, they share the same variable.

Captured vars may escape to heap

If closure outlives the defining function, Go escape analysis moves captured values from stack to heap.

Each factory call can create isolated state

Calling a closure factory twice creates separate captured environments (e.g., c and c2 counters).

# Closures as Parameters

Passing closures enables callbacks and behavior injection.

func apply(nums []int, fn func(int) int) []int {
    result := make([]int, len(nums))
    for i, v := range nums {
        result[i] = fn(v)
    }
    return result
}

doubled := apply([]int{1, 2, 3}, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2 4 6]

multiplier := 5
scaled := apply([]int{1, 2, 3}, func(n int) int { return n * multiplier })
fmt.Println(scaled) // [5 10 15]

# Real-World Example: Memoization Wrapper

Memoization is a common closure use: wrap a function and keep cache state private.

package main

import "fmt"

func Memoize(fn func(int) int) func(int) int {
    cache := make(map[int]int)
    return func(n int) int {
        if v, ok := cache[n]; ok {
            return v
        }
        result := fn(n)
        cache[n] = result
        return result
    }
}

func main() {
    calls := 0
    slow := func(n int) int {
        calls++
        return n * n
    }

    fast := Memoize(slow)
    fmt.Println(fast(5), fast(5), fast(3))
    fmt.Println("calls:", calls) // 2
}

⚡ Key Takeaways

  • Closures capture surrounding variables and can keep state across calls
  • Captured variables may escape to heap if closure outlives function scope
  • Each factory call creates independent closure state
  • Closures are ideal for callbacks, middleware, options, and wrappers
  • Memoization is a practical closure pattern for performance
practice & review