The variable has a value elsewhere in your program — just not in the scope Python already decided this line belongs to. Here's how Python actually assigns scope, and why the fix usually isn't global.
Adapted from the Python Essentials Companion Guide.
You write a small counter function, and it looks completely reasonable:
count = 0
def increment():
count += 1
return count
increment()
UnboundLocalError: local variable 'count' referenced before assignment
This one throws people harder than most, because count obviously exists — it's sitting right there on the line above, initialized to 0. The error sounds like Python has lost track of a variable that plainly exists. It hasn't. Python made a decision about this function before it ever ran a single line of it, and that decision, not a missing value, is what's crashing.
What's actually happening
Before a Python function runs, Python scans its entire body — top to bottom, in advance — looking for any name that gets assigned to anywhere inside it. If a name is assigned anywhere in the function, Python marks that name as local to the function, for the entire function body, no matter where the assignment happens to sit or whether it actually runs before other lines that use the name.
count += 1 is shorthand for count = count + 1 — it's an assignment. So Python sees an assignment to count inside increment() and decides, upfront, that count is a local variable of that function. That decision applies to the whole function, including the read on the right-hand side of count + 1, which now happens before Python has assigned anything to that local count. The count = 0 sitting above the function is a completely different variable, at module scope, that the function's local count now shadows — and it doesn't get consulted at all.
That's the whole error, restated precisely: "you're reading a local variable, at a point in the function where it hasn't been given a value yet." It's not that count doesn't exist anywhere — it's that this count, the one this function decided you meant, hasn't been assigned to yet at the line where you're reading it.
The fix, step by step
Confirm you're hitting this pattern: a variable read and assigned in the same function, where the outer/global version was meant to be reused rather than shadowed. If the traceback names a variable you expected to come from outside the function, this is almost always what's happening.
-
Decide whether the function actually needs to mutate shared state. Usually it doesn't, and that's the better fix:
count = 0 def increment(n): return n + 1 count = increment(count)Pass the value in, return the new value, and let the caller decide what to do with it. No scope trickery required, and the function is easier to test in isolation.
-
If the function genuinely needs to rebind a module-level name, tell Python that on purpose with
global:
count = 0 def increment(): global count count += 1 return countglobal countchanges Python's upfront scan: it now knows every reference tocountin this function means the module-level one, so there's no local shadow and no ordering problem. -
Check for the non-global version of this same trap, which is more common in practice: a variable assigned only inside an
ifbranch, then read after theifblock, on a code path where that branch never ran:
def classify(n): if n > 0: label = "positive" print(label) # UnboundLocalError when n <= 0This has nothing to do with global scope —
labelis only conditionally assigned, and Python doesn't know that until the function actually runs. The fix is to give it a default before the branch, or add anelse.
Two mistakes worth knowing about ahead of time
Adding global everywhere out of caution, including to functions that only read the variable. You only need global in a function that assigns to the name somewhere in its body. A function that only reads a module-level variable — never assigns to it — sees it just fine without any declaration at all. Sprinkling global on every function that merely mentions a shared name is a sign the scope model isn't fully clicked into place yet, and it tends to spread mutable shared state further than the program actually needs.
Assuming this means "the variable isn't defined yet" in a general sense, and hunting for an import order problem or a definition placed too late in the file. The variable is usually defined fine at module scope, well before the function runs. The problem isn't timing at the module level — it's that the function has its own, separate local variable of the same name, and that one hasn't been assigned yet inside this call.
A habit that prevents the confusion entirely
When a function needs to change something that lives outside it, default to passing values in and returning values out, rather than reaching into module-level state with global. It sidesteps this error category entirely, because there's no local/global name collision to trip over in the first place. Reserve global for the cases where shared mutable state is genuinely the right design — a counter tied to a long-running process, a cache, a small script where the overhead of threading values through every call isn't worth it — and even then, expect to type global explicitly the first time you assign to that name in a new function. Python won't infer the intent for you; the upfront scope scan happens the same way every time, regardless of what the variable meant one function ago.
If you'd like more posts like this sent straight to your inbox, subscribe to the newsletter.
Prefer to dig in yourself? The Python Essentials repo on GitHub has more free examples and exercises.
Top comments (0)