Skip to main content

Code blocks and scope

5 minutes read•Filed underGo Programming Languageon

Understand how Go organizes code into nested blocks and how variable scope flows through them. Learn the block hierarchy, scope rules, and how shadowing works — and why it should be avoided.

What is a block

A block is a sequence of declarations and statements enclosed in curly braces { }. Every time you write {, you open a new block. Every } closes it. Code inside a block is grouped together and, more importantly, variables declared inside a block live only within that block.

Go has a hierarchy of blocks that nest inside each other:

  • Universe block — the outermost block. Built-in identifiers like len, cap, make, true, false, and all predeclared types live here.
  • Package block — every file in a package shares this block. Package-level variables, functions, and types are declared here.
  • File block — import declarations belong here, scoped to the individual file.
  • Function block — the body of each function is its own block.
  • Statement blocks — if, for, switch, and select bodies each open a new block.
  • Explicit blocks — you can open a block anywhere inside a function using bare { }.

Scope rules

The fundamental rule is: a variable declared in an outer block is accessible in all inner blocks, but a variable declared in an inner block is not accessible in any outer block.

x is declared in the function block, so it is visible inside the if block. y is declared inside the if block, so it only exists there — once the block ends, y is gone.

Explicit blocks

You can create a new block anywhere inside a function with bare curly braces. This is useful when you want to limit the lifetime of a temporary variable to a specific section of code:

Explicit blocks are not common in everyday Go code, but they are useful in tests and in code that needs to reuse a short variable name for unrelated purposes in different sections of the same function.

Shadowing

Shadowing occurs when you declare a variable in an inner block with the same name as a variable in an outer block. The inner variable shadows the outer one — within that inner block, the name refers to the inner variable, and the outer variable becomes inaccessible:

The outer x still holds 10 after the block closes — it was never modified, just hidden. Two separate variables happened to have the same name.

Shadowing with :=

The most common source of accidental shadowing in Go is the := short declaration operator inside if, for, or switch blocks. Because := always declares a new variable in the current block, it can silently shadow an outer variable when you intended to assign to it:

The err inside the if is a brand-new variable scoped to that block, not the same err declared above. This is a frequent source of bugs where developers believe they are checking or returning the outer err but are actually working with a different one.

Why to avoid it

Shadowing is legal Go, but it reduces clarity. A reader scanning the code who sees a variable name used both before and after a block may not notice that they are actually two separate variables. The outer variable's value remains unchanged in a way that is not obvious at a glance.

The rule of thumb: if you intend to modify an outer variable from inside a block, use = (assignment), not := (declaration). Reserve := for genuinely new variables.