ProgrammingSenior Go Developer

Explain how Go implements closures (func literals/closures) and what are the limitations and features of using closures: where they are stored, how variables are captured, what is the difference in behavior of captured variables in different scenarios?

Pass interviews with Hintsage AI assistant

Answer

In Go, anonymous functions (func literals) are capable of creating closures, meaning they can access variables from the surrounding scope even after it has completed. Such closures allocate memory on the heap if necessary for proper functionality (detected via escape analysis).

Example:

func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d\n", a(5)) // 5 printf("%d\n", a(10)) // 15

Features:

  • The closure captures external variables by reference (not their values at the time of creation).
  • If a variable is altered outside the closure, the closure will see the new value.
  • If the closure is returned from a function, the captured variables will live until the closure's lifetime ends.
  • If the closure is not used, escape analysis may allow variables to not escape to the heap.

Trick Question

What will this code output?

func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }

Many would answer that it will output 0, 1, 2; however, the result will be:

3
3
3

All closures refer to the same variable i; after the loop finishes, its value is 3.

Correct Approaches: Capture a copy of the variable within the loop:

for i := 0; i < 3; i++ { v := i // new variable fs = append(fs, func() { fmt.Println(v) }) }

Examples of Real Mistakes Due to Ignorance of Subtleties


Story

In a dynamic routing project, a loop was used to create multiple handlers via closures, each intended to capture its path. As a result, all handlers printed the last path — a separate variable was not created in each closure. The error was discovered only during integration with the HTTP API.


Story

When testing concurrent access via goroutines within a loop, the closure captured a reference to the index, not a copy. This created "random" effects: data was written not to its own array slot, but to the last one.


Story

In a statistics collection function, the closure modified a global variable from the outer scope, whereas the author expected an independent counter for each task. The problem was noticed due to an inadequately reconstructed sum, which was always global, not local, despite the local logic.