PythonMastery
reference 3 min read · lesson 22 of 45 in Errors

UnboundLocalError: local variable referenced before assignment

1 · The lesson

read

What this error means

Python decides at compile time, not run time, which names inside a function are local. If a function assigns to a name anywhere in its body, that name is local for the whole function — including lines that run before the assignment. If you read it before assigning, you get UnboundLocalError.

When you see it

text
Traceback (most recent call last):
  File "counter.py", line 8, in <module>
    bump()
  File "counter.py", line 5, in bump
    print(count)
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

The classic minimal reproduction:

python
count = 0

def bump():
    print(count)   # boom: count is local, not yet assigned
    count = count + 1

bump()

Why it happens

The count = count + 1 on the last line makes count a local variable for the entire bump body. The earlier print(count) therefore looks at the local count, which has no value yet — not the module-level count = 0. Python is being consistent, not surprising: a name's scope is a property of the function, not of the line.

How to fix it

Option 1 — declare the intent with global or nonlocal. Use sparingly; mutable shared state is a smell.

python
count = 0

def bump():
    global count
    count = count + 1

For a name in an enclosing function (not module-level):

python
def make_counter():
    count = 0
    def bump():
        nonlocal count
        count += 1
        return count
    return bump

Option 2 — pass and return instead of mutating. Cleaner, testable, no shared state.

python
def bump(count):
    return count + 1

count = bump(count)

Option 3 — use a mutable container. Works because you mutate the object, not rebind the name.

python
state = {"count": 0}

def bump():
    state["count"] += 1     # no assignment to `state` itself

When you'd actually see this in real code

  • A function reads a config flag, then later reassigns it for the rest of the function ("if no value passed, use default").
  • A closure tries to update a counter in the enclosing scope but forgets nonlocal.
  • Loop accumulators inside a function where someone "improved" the code by initialising the accumulator at the top of the module instead of inside the function.
  • NameError: name 'x' is not defined — Python could not find x in any scope. Different from UnboundLocalError, where it found a local but the local has no value yet.
  • SyntaxError: name 'x' is used prior to nonlocal declaration — you used the name before declaring nonlocal x.

See Also