ProgrammingBackend developer

How are closures implemented in Go, what are the pitfalls associated with their use when launching goroutines inside loops, and how can typical mistakes be avoided?

Pass interviews with Hintsage AI assistant

Answer

In Go, closures are functions that "enclose" (capture) variables from their surrounding scope. Closures are most commonly used for anonymous functions created inside other functions.

The most typical problem when working with closures is unexpected behavior when using loop variables inside a goroutine:

for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }

Each goroutine may print the same value of i, because the variable i is looping, and the closure captures the variable itself, not its value at each iteration.

Correct way:

for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }

This behavior is related to the fact that the closure holds a reference to the variable (its address), not its sliced (by value) value.

Trick question

What value will several launched goroutines print inside a loop if they capture the loop variable?

Answer: All goroutines may print the same value (often the last one), as they see the current value of the variable, and by the time the goroutine is executed, the loop has already ended. To avoid this, the variable needs to be passed as a parameter to the closure.

Example:

for i := 0; i < 5; i++ { go func() { fmt.Println(i) }() } // most likely we will get: 5 5 5 5 5

Examples of real errors due to lack of understanding of the topic nuances


Story

In a product analytics system, data was anonymized in parallel goroutines using a closure that captured the loop variable. As a result, all parallel tasks processed the same dataset — leading to distorted statistics and incorrect reporting.

Story

In a cloud service with Go integrations, the team decided to optimize metric collection by processing them in a loop using anonymous functions — the index of the map was captured inside the goroutine, resulting in part of the handlers collecting data not for their services but for the last processed index.

Story

In a courier startup, incorrect use of closures when updating order coordinates led to mass updates of the last order's coordinates in the slice, rather than the current one — due to a race condition when accessing the slice inside the anonymous function.