Anonymous Functions and Function Literals

Understand anonymous functions and function literals in Go — how to define them, assign to variables, invoke immediately, pass as arguments, and avoid common scope mistakes.

Anonymous Function Syntax

A function literal is a function defined without a name. You write the func keyword, a parameter list, optional return types, and a body — but you omit the identifier that a regular function declaration would carry. The Go language specification calls this a function literal; most developers call it an anonymous function or lambda.

// A function literal that multiplies two integers
func(x, y int) int {
    return x * y
}

This expression stands on its own. It can appear anywhere a value is expected: assigned to a variable, passed to another function, returned from a function, or invoked immediately. The type of this particular literal is func(int, int) int.

Any function literal is also a closure: it can reference variables from the surrounding scope. That mechanism is explored fully in the closures section ahead, but it already starts here — every anonymous function you write can see the variables of its enclosing function.

Function literals vs. closures:

All function literals in Go are closures, but not every use of a closure requires an anonymous function. A named function assigned to a variable can also close over variables. This section focuses on the anonymous form.

Assigning Anonymous Functions to Variables

When you need a short helper that you plan to call multiple times but don’t want to pollute the package namespace with a named declaration, assign the function literal to a variable.

package main
import "fmt"
func main() {
    multiply := func(x, y int) int {
        return x * y
    }
    fmt.Printf("%T\n", multiply)  // func(int, int) int
    fmt.Println(multiply(4, 7))   // 28
}

The variable multiply holds a value of type func(int, int) int. You call it exactly as you would a declared function. The identifier exists only inside the block where it’s declared — it does not leak into the package scope.

This pattern is common for one-off logic inside main, for test helpers, or for adapting a small piece of behavior to a specific context.

Immediately Invoked Function Literals

If you need to execute a piece of logic once, right where you define it, add parentheses with arguments directly after the function body. This is an immediately invoked function literal, sometimes called an IIFE.

package main
import "fmt"
func main() {
    product := func(a, b int) int {
        return a * b
    }(3, 5)
    fmt.Println(product) // 15 (type int, not a function)
}

The trailing (3, 5) calls the function immediately. The variable product receives the result — int, not a function value. If you omit the parentheses, product would hold the function itself, and fmt.Println(product) would print a function address. That difference is a source of bugs.

Forgetting to invoke:

If you intend to call the function but leave off the trailing parentheses, you assign the function value instead of the result. Later code that expects a plain integer will fail to compile or produce surprising output. Always check whether you meant func(){...}() or func(){...}.

Immediately invoked literals are helpful for scoped initialization, complex one-shot computations, or running a piece of code as a statement in a context that demands an expression — for instance, in a defer that requires capturing the time at the point of deferral, not at execution time.

defer func(start time.Time) {
    fmt.Println("elapsed:", time.Since(start))
}(time.Now())

The argument time.Now() is evaluated when the defer statement runs, and the literal captures that value. If you wrote defer fmt.Println(time.Since(time.Now())), the time.Now() inside time.Since would be evaluated when the deferred function executes, giving a meaningless near‑zero duration.

Anonymous Functions as Arguments

Higher‑order functions — functions that accept other functions as parameters — are where anonymous function literals shine. The standard library’s sort.Slice is a classic example.

people := []struct {
    Name string
    Age  int
}{
    {"Zoe", 29},
    {"Al", 35},
    {"Mia", 22},
}
sort.Slice(people, func(i, j int) bool {
    return people[i].Age < people[j].Age
})
fmt.Println(people)

The second argument to sort.Slice is a comparison function. You provide the sorting logic exactly where it is used, without declaring a separate named function. The literal can access the people slice directly because it is defined inside the same scope — that’s the closure behavior at work.

When you run this, people is sorted by age in ascending order. If you swap the comparison to people[i].Name < people[j].Name, you sort by name instead. The anonymous function makes the comparison self‑contained and immediately visible.

Correct sorting output:

After running the snippet, printing the slice shows [{Mia 22} {Zoe 29} {Al 35}]. The anonymous comparator produces the expected order. If the output looks correct, your closure is capturing the slice and the indices properly.

You will encounter this pattern across the Go ecosystem: http.HandleFunc, context.WithCancel, sync.Once.Do, testing helpers like t.Run, and many more. An anonymous function lets you express behavior right at the call site without indirection.

Returning Anonymous Functions

A function can return another function — often one that wraps captured state from the outer call. This is a factory pattern that creates configured behaviors.

func multiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}
func main() {
    double := multiplier(2)
    triple := multiplier(3)
    fmt.Println(double(5)) // 10
    fmt.Println(triple(5)) // 15
}

multiplier takes a factor and returns an anonymous function that multiplies its argument by that factor. Each call to multiplier creates a distinct closure with its own factor value. double and triple are independent function values that remember the factor they were created with.

Returning a function literal gives you a lightweight way to build parameterized handlers, middleware, or iterators. The pattern is used heavily in HTTP server middleware, where a middleware function returns a handler that wraps the next handler in the chain.

Variable Capture and Closure Behavior

Every function literal can reference variables from the enclosing function. Those variables are not copied at the time the literal is defined — the literal uses the same variable, meaning it sees the current value each time it runs. The variable lives as long as any closure that references it.

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}
func main() {
    next := counter()
    fmt.Println(next()) // 1
    fmt.Println(next()) // 2
    fmt.Println(next()) // 3
}

The inner function captures count. Each call to next increments the same count variable; the state persists across calls because the closure keeps the variable alive.

This same sharing behavior becomes a pitfall when the enclosing variable changes after the literal is created. The classic case is a loop variable.

// Pre‑Go 1.22 behavior (and still valid code)
for _, v := range []int{10, 20, 30} {
    go func() {
        fmt.Println(v) // all three goroutines print 30 (the final value)
    }()
}

Before Go 1.22, the loop variable v was reused per iteration. The goroutines captured the same variable, and by the time they ran, the loop had finished and v held the last value. The fix was to pass v as an argument:

for _, v := range []int{10, 20, 30} {
    go func(val int) {
        fmt.Println(val) // prints 10, 20, 30 (order non‑deterministic)
    }(v)
}

Since Go 1.22, each loop iteration creates a new variable, so the straightforward go func() { fmt.Println(v) } works as expected. You will still encounter the older idiom in existing codebases, and the argument‑passing approach remains the clearest way to make the intended capture explicit, even with modern Go.

Loop variable capture in older Go code:

If you are reading a codebase that targets Go 1.21 or earlier, closures inside loops that capture the iteration variable will exhibit the reuse behavior. Look for the go func(val int) { ... }(v) pattern or a local shadow v := v inside the loop — those signal intent to capture the per‑iteration value.

Common Mistakes and Pitfalls

Expecting a copy of the variable at definition time

Closure captures are references to the variable itself, not snapshots of its value. If the variable mutates before the closure runs, the closure sees the mutated value. This is by design, but it surprises newcomers who assume the function “remembers” the value at creation time.

msg := "hello"
defer fmt.Println(msg)  // prints "hello"
msg = "world"
// defer still prints "hello" because arguments are evaluated immediately

However, with an anonymous function:

msg := "hello"
defer func() { fmt.Println(msg) }()
msg = "world"
// prints "world" — the closure reads msg at execution time

Both behaviors are correct and useful in different contexts. Know which one your use case requires.

Unintentionally shadowing the outer variable

Inside a function literal, you can declare a new variable with the same name as an outer one using short variable declaration (:=). This creates a new local that shadows the captured variable, and modifications no longer affect the outer scope.

count := 10
func() {
    count := 5 // new local, does not touch outer count
    fmt.Println(count) // 5
}()
fmt.Println(count) // 10

If your intent was to mutate the captured variable, use assignment = without the colon.

Using defer inside a loop with closures

Deferring inside a loop builds up a stack of deferred calls that all execute when the surrounding function returns, which may be far in the future and lead to resource leaks. Wrapping the loop body in an immediately invoked anonymous function ensures the deferred call runs at the end of each iteration.

for _, file := range files {
    func() {
        f, err := os.Open(file)
        if err != nil {
            return
        }
        defer f.Close()
        // process f
    }()
}

The anonymous function creates a scope where defer fires at the end of each iteration rather than accumulating until the function exits.

When to Use Anonymous Functions

Use an anonymous function when the logic is short, appears exactly once, and naming it would add noise rather than clarity. Good candidates:

  • Comparators and predicates passed to sorting, searching, or filtering functions.
  • One‑shot setup or teardown logic inside main or test functions.
  • Inline behavior for goroutines: go func() { ... }().
  • Simple adapters where the function signature must match an expected type but the implementation is trivial.
  • Deferred cleanup that needs to capture state at the point of the defer statement.

If the same logic is reused in multiple places, move it to a named function. If the logic grows beyond a few lines, extracting it into a named function often improves readability and testability.

Summary

Anonymous function literals give you a way to define behavior exactly where you need it, without introducing a package‑level or file‑level name. You can assign them, invoke them on the spot, pass them as arguments, and return them as values. They are the building block for closures, which bind the function body to variables from the enclosing scope.