Skip to content
datarekha
Python Medium Asked at AmazonAsked at GoogleAsked at Microsoft

Explain Python's LEGB scope rule with an example.

The short answer

Python resolves names by searching four scopes in order: Local, Enclosing, Global, then Built-in. The first match wins. Assignment in a scope always creates or modifies a name in that scope unless global or nonlocal overrides this.

How to think about it

LEGB usually arrives as a warm-up — a way to reach the real targets, closures and the global/nonlocal keywords. The acronym itself is easy. The part worth your breath is one subtle rule: the moment a name is assigned anywhere inside a function, Python treats it as local for the entire body — even on lines above the assignment.

First the four scopes, searched innermost to outermost:

  1. Local — the function currently running.
  2. Enclosing — any outer functions, for closures, searched inside-out.
  3. Global — the module’s namespace.
  4. Built-in — Python’s own names (len, range, print, …).

Reading a name walks L→E→G→B and stops at the first hit. Writing a name always targets Local, unless you say global or nonlocal.

A worked example

x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print("inner sees:", x)      # L wins
    inner()
    print("outer sees:", x)          # E is innermost for outer()

outer()
print("module sees:", x)             # G

print()

# nonlocal lets an inner function rebind the enclosing variable
def make_counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

c = make_counter()
print("counter:", c(), c(), c())

print()

# Built-ins live in the B layer — and can be shadowed (don't do this!)
print("len is built-in:", len([1, 2, 3]))
len = lambda x: "oops"
print("shadowed len:", len([1, 2, 3]))
del len                              # remove the shadow
print("restored len:", len([1, 2, 3]))
inner sees: local
outer sees: enclosing
module sees: global

counter: 1 2 3

len is built-in: 3
shadowed len: oops
restored len: 3

Each print resolved x from its own scope outward — three different answers for one name. The counter shows nonlocal at work, and the len block is a cautionary tale: assigning len at module level shadows the built-in everywhere below it, and only deleting the shadow brings the real one back.

The classic trap — UnboundLocalError

counter = 0
def increment():
    counter += 1     # UnboundLocalError: counter referenced before assignment

Python sees counter += 1, decides counter is local for the whole function, and then the read half of += runs before any local value exists — so it raises. The fix is to declare intent: global counter for a module variable, nonlocal count for an enclosing one.

Learn it properly Functions

Keep practising

All Python questions

Explore further