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:
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
globalornonlocal
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:
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:
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:
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 += 1is still an assignment and therefore creates a local by default. - Using
globalwhen the variable actually belongs to an enclosing function scope and needsnonlocal. - 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
- '
UnboundLocalErrorin 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
nonlocalto rebind a variable from the nearest enclosing function scope. - Use
globalonly for module-level names. - Remember that closures capture bindings, and assignment changes how Python classifies the name.

