Explain Python's LEGB scope rule with an example.
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
What the interviewer is really testing
LEGB is often asked as a warm-up to see if you understand closures and the global/nonlocal keywords. The interesting part is not the acronym — it’s explaining the subtle rule that any assignment inside a function makes Python treat that name as local for the entire function body, even lines above the assignment.
The four scopes, from innermost to outermost
- Local — inside the currently executing function.
- Enclosing — any outer function scopes (for closures), searched inside-out.
- Global — module-level namespace.
- Built-in — Python’s built-ins (
len,range,print, …).
Python stops at the first scope where the name is found. Reading uses LEGB; writing always targets Local unless you use global or nonlocal.
Step-by-step walkthrough
x = "global" # G
def outer():
x = "enclosing" # E
def inner():
x = "local" # L
print(x) # "local" — L wins
inner()
print(x) # "enclosing" — E is innermost for outer()
outer()
print(x) # "global"
Each print resolves x starting from its own local scope and working outward.
The classic trap — UnboundLocalError
counter = 0
def increment():
counter += 1 # UnboundLocalError: counter read before assignment
Python sees the assignment counter += 1 inside the function and decides counter is a local variable — for the whole function. But it has not been assigned yet when the read-side of += executes, so you get UnboundLocalError. The fix:
def increment():
global counter
counter += 1
For closures, use nonlocal:
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
Interactive playground — experiment with all four scopes
The key insight
The rule “any assignment makes the name local for the whole function” is what makes Python predictable. Without it, reading a global before a local assignment would silently return the global value up until the assignment line — an order-dependent surprise. Python’s approach surfaces the bug loudly instead.