Backend Development 11 min read

Understanding and Avoiding Pitfalls of Closures in Go

The article explains Go closures, shows how capturing loop variables by reference can cause bugs like repeated values, demonstrates correct patterns such as copying variables inside loops or passing them as parameters, and offers guidelines to avoid common pitfalls with defer and concurrency.

Bilibili Tech
Bilibili Tech
Bilibili Tech
Understanding and Avoiding Pitfalls of Closures in Go

The article introduces the concept of closures in Go, explains why they can cause subtle bugs, and provides practical guidelines to use them correctly.

Background

A closure is a function together with the lexical environment that captures variables from its surrounding scope. Misusing closures—especially inside loops—can lead to unexpected results, as illustrated by a real incident where a closure bug caused a production failure.

Problem illustration

The following Go code stores anonymous functions (closures) in a slice and later executes them:

func main() {
    // Save function closures
    var s []func()
    for _, v := range []string{"a", "b", "c", "d", "e"} {
        s = append(s, func() {
            fmt.Printf("value: %v\n", v)
        })
    }
    for _, f := range s {
        f()
    }
}

One might expect the output to be a b c d e , but the actual result is:

value: e
value: e
value: e
value: e
value: e

The reason is that the loop variable v is captured by reference; all closures share the same variable, which ends up holding the last value ( e ) after the loop finishes.

Correct usage

Declare a new variable inside the loop so each closure captures its own copy:

func main() {
    var s []func()
    for _, v := range []string{"a", "b", "c", "d", "e"} {
        v := v // create a new variable for this iteration
        s = append(s, func() {
            fmt.Printf("value: %v\n", v)
        })
    }
    for _, f := range s {
        f()
    }
}

Now the output matches the expectation:

value: a
value: b
value: c
value: d
value: e

Additional examples

Anonymous functions can also be used with defer . The following demonstrates the difference between a closure (reference capture) and a regular value capture:

func main() {
    i := 0
    // Closure: i is captured by reference
    defer func() {
        fmt.Println("defer closure i:", i)
    }()
    // Non‑closure: i is passed by value
    defer fmt.Println("defer i:", i)
    i = 100
    return
}
// Output
// defer i: 0
// defer closure i: 100

Common pitfalls and how to avoid them

Using a closure inside a for range loop without copying the loop variable.

// Wrong – prints 3 3 3
s := []int{1, 2, 3}
for _, v := range s {
    go func() { fmt.Println(v) }()
}
select {}

// Correct – prints 1 2 3
s := []int{1, 2, 3}
for _, v := range s {
    go func(v int) { fmt.Println(v) }(v)
}
select {}

Using a closure in a defer statement where the captured variable changes after the defer is set.

// Wrong – y becomes 102 before the deferred function runs
package main
import "fmt"
func main() {
    x, y := 1, 2
    defer func(a int) {
        fmt.Printf("x:%d,y:%d\n", a, y) // y is 102
    }(x)
    x += 100
    y += 100
}

// Fixed – avoid defer with a closure or copy needed values
package main
import "fmt"
func main() {
    x, y := 1, 2
    func(a int) {
        fmt.Printf("x:%d,y:%d\n", a, y) // y is 2
    }(x)
    x += 100
    y += 100
    fmt.Println(x, y)
}

Summary

Closures are powerful for creating private state, callbacks, and higher‑order functions, but they must be used with care. Pay attention to variable scope, avoid capturing loop variables directly, and be mindful of how defer interacts with closures to prevent unexpected behavior and potential memory or performance issues.

backend developmentprogrammingGoPitfallAnonymous FunctionClosure
Bilibili Tech
Written by

Bilibili Tech

Provides introductions and tutorials on Bilibili-related technologies.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.