How does Python's LEGB scoping rule work?

6 minintermediatefundamentalsscopingclosures

Quick Answer

Python resolves a name by searching, in order: **L**ocal (current function) → **E**nclosing (any enclosing function's scope, for closures) → **G**lobal (module level) → **B**uilt-in (`builtins` module). Assignment inside a function makes a name local by default *for that entire function body*, unless declared `nonlocal` or `global` — which is why assigning to a name before using it can raise `UnboundLocalError`.

Detailed Answer

The four scopes, in lookup order

x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)          # 'local'   -- found in Local scope
    inner()
    print(x)               # 'enclosing'

outer()
print(x)                    # 'global'
print(len)                  # built-in, found in Built-in scope

When Python looks up a bare name, it checks Local, then Enclosing function scopes (innermost to outermost), then Global (module), then Built-in — the first scope where the name is bound wins.

The gotcha: assignment makes a name local for the whole function

Python decides whether a name is local to a function at compile time, by scanning the function body for assignments — not by checking whether the assignment has "already happened" at runtime.

count = 0

def increment():
    print(count)     # UnboundLocalError!
    count = count + 1

Because count = ... appears anywhere in increment, Python treats count as local for the entire function body — including the print(count) line before the assignment. It never falls back to the global count.

Fixing it: global and nonlocal

count = 0

def increment():
    global count
    count += 1          # now refers to the module-level count

def make_counter():
    total = 0
    def add(n):
        nonlocal total   # refers to make_counter's `total`, not a new local
        total += n
        return total
    return add
  • global binds a name to the module-level scope.
  • nonlocal binds a name to the nearest enclosing function scope (not global) — this is what makes stateful closures possible.

Why this matters for closures

The "E" in LEGB is exactly what lets a nested function remember variables from its enclosing function after that function has returned — the classic closure pattern (make_counter above). Without nonlocal, a nested function can read an enclosing variable freely, but assigning to it creates a new local instead of updating the enclosing one.

Interview-ready summary: Name resolution follows Local → Enclosing → Global → Built-in, and whether a name is "local" is decided statically by scanning for assignments in the function body — which is why referencing a name before assigning it in the same function raises UnboundLocalError instead of falling back to an outer scope. global and nonlocal are the explicit escape hatches for writing to an outer scope.