FULL PYTHON FIX · 8 MIN READ

How to Fix UnboundLocalError in Python

A function tries to read a counter before its local assignment has given it a value. Trace the scope rule, then pass the current value in and return the update.

Before you start: Functions · Variables and Types

Video guide: How to Fix UnboundLocalError in Python

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Visits is zero before the function definition. The name exists, yet calling record_visit still crashes. Why does Python say that the local variable visits has no value? The traceback points to visits plus equals one, so we will focus on that operation instead of guessing about the final print. I am running the broken version first so we can see the exact exception. UnboundLocalError means a local name was read before it was given a value in this function call. Some Python versions phrase the message as local variable referenced before assignment; others say it cannot access the local variable where it is not associated with a value.

  2. 00:42

    The same scope issue is behind both messages. Let us work out why Python considers visits local at all. We will then choose a repair that makes the counter's input and output visible, and test that it works with two independent counters rather than one hidden global value. The shorter visits plus equals one first reads the old visits value and then stores the new one. The expanded version makes that order easier to see. The right-hand visits must have a value before the left-hand assignment can complete. Within a function body, assigning to a name normally marks that name as local throughout the body.

  3. 01:22

    Python therefore looks for a local visits value during this read. None has been assigned in this call yet. The outer visits is a different binding, so it does not fill the missing local slot. For contrast, a function that only prints visits and never assigns to it can read the outer value. But that does not mean our counter update should depend on a module-level name. A function that takes its current count as an argument is easier to reuse and easier to test. The error tells us about the scope rule; the design choice is how this function should receive state. Now the function receives current as a parameter. The caller passes its visits value into the call.

  4. 02:04

    Inside the function, current already has a value, so current plus one can be evaluated safely. The function returns the incremented number, and the caller decides to store that result back in visits. Repeating that sequence twice moves zero to one, then one to two. There is no hidden mutation of a module variable. We could call record_visit with nine and get ten without resetting anything else. A global declaration would also make the original tiny example run, but it would make every call edit the same module-level variable. Use global only if shared module state is genuinely the intended contract.

  5. 02:44

    In a nested function, nonlocal can refer to a variable in the enclosing function; that is a different scope relationship. Neither declaration should be a reflexive patch for an ordinary helper that can take an argument. The two counters show that one call changes no state unless the caller stores its result. First becomes two and second becomes eleven. That is useful evidence that the helper depends on the value passed to it, not on one global counter. UnboundLocalError can also appear without a global-looking counter. Here label is assigned only when score is at least fifty. For forty, the if body is skipped and return tries to read a local name that has never received a value.

  6. 03:29

    You can repair this by returning a value in each branch, initializing the name before the condition, or explicitly representing a missing result. For example, an else branch could return fail directly, while the true branch returns pass. Both paths then have a defined result, and no local label needs to exist at all. Follow every possible path through the function. The traceback identifies the read that failed; then trace back to the assignment that should have happened before that read. Your practice task is to write record_sale. It receives the running total and the new amount, and returns their sum. In the caller, start total at zero, process a sale of eight and a sale of five, and print thirteen.

  7. 04:14

    Then call the function with one hundred and seven; that independent call should return one hundred seven. If your result depends on an outer variable called total, the check will catch it. Try creating two separate totals in the caller, one for morning sales and one for afternoon sales. Pass each value to the same function, and check that an afternoon update cannot change the morning result. This demonstrates why explicit state is useful beyond merely avoiding an exception: the function can serve several independent calculations. When you see UnboundLocalError, expand an augmented assignment mentally into a read and a write.

  8. 04:55

    Ask whether the name is local because the function assigns to it, and whether that local value exists on this path. For a caller-owned running value, pass it in and return the update. For conditional code, test the path that skips an assignment. A test for just the successful branch may conceal an unset local variable until real input reaches the other branch. The guide has every example in the interactive console and links back to the functions lesson.

Run a counter update that fails inside a function

A program has a visits counter and a helper intended to add one visit. The name visits exists before the function call, so the error can seem impossible. Run the program and read the last line of the traceback. The exception occurs on visits += 1 inside record_visit, before the later print can run.

The text of the exception differs slightly between Python versions, but the type UnboundLocalError is the important clue. It means this function has a local variable that it tries to use before that local variable has a value. The outer visits value does not automatically become the local value. Keep the failing example small before deciding how to change the design.

The failing counter
PYTHON
visits = 0

def record_visit():
    visits += 1
    return visits

print(record_visit())
ERROR (LAST LINE)
UnboundLocalError: cannot access local variable 'visits' where it is not associated with a value

Expand the augmented assignment

Within a function body, assigning to a name normally makes it local throughout that body unless a global or nonlocal declaration changes the rule. The expression visits += 1 must first read the existing visits value and then assign the new value. Because this function assigns visits, Python treats it as a local name; there is no earlier local value to read.

Writing visits = visits + 1 produces the same problem. The right-hand visits is still a read before the left-hand assignment. By contrast, a function that only prints visits can read the outer value, because that function never assigns to the name. That observation helps explain the error but does not make hidden outer state a good input for a reusable counter function.

The expanded expression has the same scope problem
PYTHON
visits = 0

def record_visit():
    visits = visits + 1
    return visits

record_visit()
ERROR (LAST LINE)
UnboundLocalError: cannot access local variable 'visits' where it is not associated with a value
A read without assignment behaves differently
PYTHON
visits = 0

def show_visits():
    print(visits)

show_visits()
OUTPUT
0

Pass the current count and return the next one

Give the function the value it needs as an argument. The parameter current is local and already has a value when the body runs. The function returns current + 1, and the caller assigns that returned value to visits. This repair makes the data flow visible at the call site. You can test record_visit by itself without preparing a module-level variable.

A global declaration could make the original example run, but then every call edits one shared module variable. That can be appropriate for deliberately shared module state, not as a reflexive answer to every UnboundLocalError. A nonlocal declaration applies to an enclosing function's variable, not a module-level variable. Choose those only when shared state is part of the intended design.

Explicit input and returned result
PYTHON
def record_visit(current):
    return current + 1

visits = 0
visits = record_visit(visits)
visits = record_visit(visits)
print(visits)
OUTPUT
2
The helper works independently of a global name
PYTHON
def record_visit(current):
    return current + 1

print(record_visit(0))
print(record_visit(9))
OUTPUT
1
10

Check repeated updates and separate counters

Test the value returned for zero and a nonzero count, then maintain two independent counters. Updating the first must not silently change the second. This catches a repair that accidentally depends on one global visits name instead of its argument. The caller, not the helper, decides which counter receives the result.

If your real code raises UnboundLocalError with a name inside an if branch, inspect every path through the function. A name assigned only when a condition is true may be read on a false path before assignment. Initialize it before the branch, return early, or handle the missing case explicitly. The traceback identifies the read that failed; trace backward to find where that local name should have received a value.

Two counters stay separate
PYTHON
def record_visit(current):
    return current + 1

first, second = 0, 10
first = record_visit(first)
first = record_visit(first)
second = record_visit(second)
print(first, second)
OUTPUT
2 11
A conditional path can leave a local name unset
PYTHON
def label_score(score):
    if score >= 50:
        label = 'pass'
    return label

print(label_score(40))
ERROR (LAST LINE)
UnboundLocalError: cannot access local variable 'label' where it is not associated with a value
  • Assignment inside a function normally marks that name as local in the function.
  • An augmented assignment first reads the old value, then writes the new value.
  • Passing state in and returning the result makes independent calls easy to test.

Practice the fix

Write record_sale(total, amount) to return the updated total without reading or changing a global variable. Start total at 0 in the caller, process two sales of 8 and 5, and print 13. Check that record_sale(100, 7) returns 107 independently.

Need a hint?

The parameter total already contains the current value. Return total + amount. In the caller, write total = record_sale(total, amount).

Python console

Ready to run

Edit the code and run it in your browser. Examples above can be loaded with Try this example.

Does your code use input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.

Write your solution, then select Check practice.

    Key takeaways

    • UnboundLocalError identifies a read of a local variable before it has a value.
    • Assignment or augmented assignment inside a function normally makes that name local.
    • Pass the current value as an argument and return the update when the state belongs to the caller.
    • Inspect conditional branches too: some paths may skip the assignment.

    Related lessons

    Sources and further reading