The Error
UnboundLocalError: local variable 'x' referenced before assignment
This one bites me every few months. You have a variable that clearly exists, you're reading it inside a function, and Python throws a fit. The confusing part: the variable does exist β just not locally. Python decided it should be local before you ever assigned it.
Why Python Throws This
Python determines whether a variable is local or global at compile time, not at runtime. Spot any assignment to a name anywhere in a function body, and Python marks that name as local for the entire function β including lines that come before the assignment.
Classic trap:
count = 0
def increment():
print(count) # <-- UnboundLocalError here
count += 1 # Python sees this assignment β marks 'count' as local
increment()
count exists at module scope. Doesn't matter. The count += 1 line is enough to make Python treat the whole function's count as local. So the print(count) above it tries to read a local variable that hasn't been assigned yet. Bang.
Step-by-Step Fix
Fix 1: Use global when modifying a module-level variable
When the variable lives at module scope and you need to modify it inside a function, declare it explicitly:
count = 0
def increment():
global count # tell Python: 'count' refers to the module-level variable
print(count) # reads module-level count β 0
count += 1
increment()
print(count) # 1
One caveat: only use global when you genuinely need to mutate the outer variable. If you're just reading it β no assignment anywhere in the function β you don't need global at all.
Fix 2: Use nonlocal for nested functions (closures)
Same problem, different scope. When the variable belongs to an enclosing function rather than module scope:
def outer():
count = 0
def inner():
nonlocal count # refers to outer()'s 'count'
count += 1
print(count)
inner() # prints 1
inner() # prints 2
outer()
nonlocal landed in Python 3. In Python 2, closures couldn't rebind outer names β the usual workaround was wrapping the value in a mutable container like a single-element list: count = [0], then count[0] += 1.
Fix 3: Initialize the variable locally first
Sometimes the real fix is simpler β you're accidentally re-using a name that shadows an outer variable:
# Broken: tries to read 'result' before it's assigned
def compute(data):
if data:
result = process(data)
return result # UnboundLocalError if data is falsy
# Fixed: give it a default
def compute(data):
result = None
if data:
result = process(data)
return result
Fix 4: Restructure to avoid the mutation entirely
Honestly, this is usually the cleanest path. Pass the value in, return the new value, skip globals altogether:
count = 0
def increment(n):
return n + 1
count = increment(count)
print(count) # 1
Pure functions are easier to test. They also make this entire category of bug impossible.
Less Obvious Cases
Conditional assignment
x = 10
def maybe_assign(flag):
if flag:
x = 99 # Python marks 'x' as local for the whole function
print(x) # UnboundLocalError when flag is False
maybe_assign(False)
Two options: initialize x at the top of the function body, or declare global x. Either works; the initializer approach is usually cleaner.
Augmented assignment (+=, -=, etc.)
count += 1 is syntactic sugar for count = count + 1. The assignment on the left makes Python classify count as local. The right side then tries to read that (not-yet-assigned) local. This is the single most common trigger for this error β worth burning into memory.
Inside try/except blocks
def load():
try:
data = fetch()
except Exception:
pass
return data # UnboundLocalError if fetch() raised
When fetch() raises, the assignment never happens and data stays unbound. Fix: add data = None before the try block.
Verify the Fix
Run the function directly with the input that originally triggered the error:
python your_script.py
# or in a REPL:
>>> increment()
0
>>> print(count)
1
No UnboundLocalError and the return value looks right? You're done.
For the conditional-assignment case, test both branches β flag=True and flag=False. The error only appears on one path, so a single passing test isn't enough.
Quick Cheat Sheet
- Modifying a module-level variable inside a function β add
global varname - Modifying an enclosing function's variable β add
nonlocal varname - Variable only assigned in one branch of an if/try β initialize before the branch
- Just reading, not assigning β no keyword needed; Python can already see the outer scope
- Best long-term fix β avoid mutable globals; pass values as parameters and return results

