ProgrammingBackend Go Developer

Explain how variable naming and scope work in Go with nested functions and loops. What pitfalls should be considered?

Pass interviews with Hintsage AI assistant

Answer.

In Go, the rules of variable scoping are strictly defined by blocks ({}), and a variable name can be shadowed in nested scopes. Many traps occur especially in nested functions, anonymous functions, loops, and when declaring variables with the same name at different levels.

Background

Go has specifically minimized "magical" behavior concerning scope to make the code more readable. However, the flexibility of the syntax and the allowance for redeclaring variables through the short form := often leads to misunderstandings.

Problem

If a variable is declared in a nested function or within a loop block with the same name as at the higher level, the outer variable becomes inaccessible (shadowed). In most cases, this does not get noticed by the compiler and can easily lead to errors, especially when working with closures. Another common mistake is declaring a new variable in an if block or in a for-init, and then attempting to access it outside the block.

Solution

Always keep an eye on the levels of scope. Do not use the same variable names in nested blocks or anonymous functions without real necessity, avoid short names, and be careful with the usage of :=.

Example code:

package main import "fmt" func main() { x := 1 { x := 2 // shadows x from main() fmt.Println("Inner x:", x) } fmt.Println("Outer x:", x) for i := 0; i < 3; i++ { x := i // a new x is created on each iteration go func() { fmt.Println("Goroutine x:", x) }() } }

In this example, the outer variable x is not modified; a new x is created within the block. In the second loop, the variable x is captured in the nested function — the result may be unexpected.

Key features:

  • Each scope (block) can shadow variables from the upper level;
  • Two variable declarations with the same name are unrelated if they are in different scopes;
  • Closures capture the variable, not its value at the time of iteration;
  • The short form := inside a block always creates a new variable, even if an outer one already exists.

Tricky questions.

1. What value of the variable will be printed last in the nested block when shadowed?

The value of the outer variable, because the inner variable exists only within the block.

2. What will happen if you try to access a variable declared inside an if/for block outside that block?

The compiler will throw an error: the variable is out of scope.

if true { y := 5 } fmt.Println(y) // error

3. How to avoid unexpected values when creating goroutines in a loop using a variable?

Pass the variable as a parameter to the function:

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

Typical errors and anti-patterns

  • Using the same identifier at different levels — data is lost and hard to track;
  • Capturing loop variables in goroutines without passing them explicitly as arguments;
  • Expecting that the short form := will modify an existing variable (it will create a new one).

Real-life example

Negative case

A loop initializes several goroutines for parallel processing, but the loop variable is used inside a closure without passing it — all goroutines operate with its "last" value.

Pros:

  • Concise, little code.

Cons:

  • Unpredictable behavior, bugs in production, data is lost.

Positive case

Passing the loop variable as a parameter to the closure — each goroutine receives its value.

Pros:

  • Correct operation, no data races or surprises.

Cons:

  • Requires explicitly defining the function parameter list.