Python
UnboundLocalError
closure
duplicate-question
debugging

Why does this UnboundLocalError occur closure?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

UnboundLocalError in a closure usually happens because Python decides that a name is local to the inner function as soon as that function assigns to it anywhere. Once that happens, reading the name before the local assignment is treated as reading an uninitialized local variable, even if an outer variable with the same name exists.

See the Problem in a Small Example

This code raises UnboundLocalError:

python
1def outer():
2    count = 0
3
4    def inner():
5        print(count)
6        count += 1
7
8    inner()
9
10outer()

The surprising part is that count clearly exists in outer. But inside inner, Python sees count += 1 and concludes that count is a local variable of inner. Then print(count) becomes an attempt to read that local before assignment.

Python Resolves Scope at Compile Time

The important rule is not “Python checks the nearest value dynamically”. The real rule is:

  • if a function assigns to a name, that name is local to that function by default
  • unless declared global or nonlocal

This decision is made when Python compiles the function body, not while it is running line by line.

That is why the error occurs even though a human might think, “print the outer value first, then update it”.

Use nonlocal for Enclosing Scope Variables

If you want the inner function to modify the variable from the enclosing function, declare it nonlocal:

python
1def outer():
2    count = 0
3
4    def inner():
5        nonlocal count
6        print(count)
7        count += 1
8
9    inner()
10    print(count)
11
12outer()

Now Python knows count should come from the nearest enclosing function scope rather than from a new local variable inside inner.

global Is Different

If the variable lives at module scope instead of an enclosing function scope, use global:

python
1count = 0
2
3def bump():
4    global count
5    count += 1
6
7bump()
8print(count)

global and nonlocal solve different problems. Mixing them up is a common source of confusion.

Closures Capture Names, Not Frozen Values

Another useful mental model is that closures capture variable bindings, not snapshots of values. The inner function refers to the name from the enclosing scope. If you later rebind or assign to that name in the inner function without nonlocal, Python treats it as a new local variable and the link to the outer binding is broken.

That is why mutation and reassignment behave differently. For example, this works without nonlocal because you are mutating an outer object rather than rebinding the name:

python
1def outer():
2    items = []
3
4    def inner():
5        items.append("x")
6        print(items)
7
8    inner()
9
10outer()

No assignment to items occurs in inner, so Python keeps using the enclosing binding. That distinction explains a large share of “why does one version work and the other fail?” closure questions in Python.

Common Pitfalls

  • Assuming Python decides scope based on where a variable happens to be read first.
  • Forgetting that x += 1 is still an assignment and therefore creates a local by default.
  • Using global when the variable actually belongs to an enclosing function scope and needs nonlocal.
  • Confusing mutation of an outer object with reassignment of the outer variable name.
  • Treating closures as if they store a frozen copy of outer values rather than a binding.

Summary

  • 'UnboundLocalError in closures usually means Python treated the name as a local variable because of an assignment in the inner function.'
  • The error happens when that local is read before it has been assigned.
  • Use nonlocal to rebind a variable from the nearest enclosing function scope.
  • Use global only for module-level names.
  • Remember that closures capture bindings, and assignment changes how Python classifies the name.

Course illustration
Course illustration

All Rights Reserved.