# UnboundLocalError: cannot access local variable where it is not associated with a value in Python: 10 Causes and Fixes (Tested Examples)

**Python raises `UnboundLocalError: cannot access local variable 'x' where it is not associated with a value` when a function reads a name that Python has decided is local to that function, but the line that gives it a value has not run yet. Any assignment to a name anywhere inside a function (including `+=`, `for`, `import`, `del` and `except ... as`) makes that name local for the whole function. The usual fixes are to give the variable a value before any branch or loop, pass values in and return them, or declare `global` or `nonlocal` when you really mean an outer variable.**

On Python 3.10 and older the same error reads `UnboundLocalError: local variable 'x' referenced before assignment`. It is the same problem with a friendlier message in newer versions. The Python docs list [UnboundLocalError](https://docs.python.org/3/library/exceptions.html) as a subclass of `NameError`, raised for a local variable that has no value bound to it yet.

The confusing part is that the variable often *does* exist, right at the top of the file. I hit it myself on my first visit counter. I reproduced every example below on Python 3.13.5, and I re-ran the first one on 3.10.22, 3.11.17, 3.14.8 and 3.15.0rc3 to compare the messages. Paths in the tracebacks point at my test scripts under `/workspace/seo-posts-nov08/test/python/`.

**In this guide:**

- What the error looks like
- Why Python thinks the variable is local
- The 10 causes at a glance
- 1. Augmented assignment (+=) to a global
- 2. Reading a global, then assigning it later
- 3. A variable assigned only inside an if branch
- 4. Using a loop variable after an empty loop
- 5. A try block that failed before the assignment
- 6. A nested function updating an outer variable
- 7. Reading a name after del
- 8. An import later in the same function
- 9. Using an except ... as variable after the block
- 10. Shadowing a built-in like len
- How to find the cause in under a minute
- Frequently asked questions

## What the error looks like

Here is the classic version: a module-level counter and a function that tries to bump it.

```
count = 0

def add_visit():
    count += 1
    return count

print(add_visit())
```

```
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c01_global_counter.py", line 7, in <module>
    print(add_visit())
          ~~~~~~~~~^^
  File "/workspace/seo-posts-nov08/test/python/c01_global_counter.py", line 4, in add_visit
    count += 1
    ^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
```

The carets point at `count` on the `+=` line. Python 3.11.17, 3.14.8 and 3.15.0rc3 printed the same final message. Python 3.10.22 printed the older wording, `UnboundLocalError: local variable 'count' referenced before assignment`, without the caret lines. Same bug, same fixes.

## Why Python thinks the variable is local

Python decides the scope of every name when it compiles a function, before any line runs. The rule from the [execution model](https://docs.python.org/3/reference/executionmodel.html) is simple: if a name is bound anywhere in a function body, it is local to that whole function, unless you declare it `global` or `nonlocal`. Position does not matter. An assignment on the last line still makes the name local on the first line.

So `count += 1` means "read local `count`, add 1, store local `count`". The read happens first, the local has no value yet, and Python refuses to quietly fall back to the global with the same name. That refusal is a good thing. A silent fallback would make bugs far harder to spot.

You can watch the compiler's decision yourself. This script inspects the function before calling it:

```
total = 100

def report():
    print("total is", total)
    total = 0

print("UnboundLocalError is NameError subclass:", issubclass(UnboundLocalError, NameError))
print("local names in report:", report.__code__.co_varnames)

import symtable
src = open(__file__).read()
table = symtable.symtable(src, __file__, "exec")
fn = table.lookup("report").get_namespaces()[0]
sym = fn.lookup("total")
print("total is_local:", sym.is_local(), "is_global:", sym.is_global())

try:
    report()
except NameError as e:
    print("caught by except NameError:", type(e).__name__, "-", e)
```

```
UnboundLocalError is NameError subclass: True
local names in report: ('total',)
total is_local: True is_global: False
caught by except NameError: UnboundLocalError - cannot access local variable 'total' where it is not associated with a value
```

`co_varnames` and `symtable` both list `total` as local before the function runs. And because `UnboundLocalError` subclasses `NameError`, `except NameError` catches both.

## The 10 causes at a glance

Every cause below comes back to the same rule: something assigns the name inside the function, and a read runs before that assignment. Use this table to jump to the one that matches your code.

| # | Cause | What you see | Fix |
| --- | --- | --- | --- |
| 1 | `count += 1` on a global | Fails on the first call | Return a new value, or declare `global` |
| 2 | Read a global, assign it later in the same function | Fails on the read, not the assignment | Rename the local or use `global` |
| 3 | Assigned only inside an `if` | Works for some inputs only | Set a default before the `if` |
| 4 | Loop variable after an empty loop | Works until the list is empty | Initialise before the loop |
| 5 | `try` failed before the assignment | Error inside `except`/`finally` | Use `with` or set `None` first |
| 6 | Closure updates an outer variable | Inner function fails on `+=` | Declare `nonlocal` |
| 7 | `del x`, then read `x` | Fails after the delete | Do not reuse a deleted name |
| 8 | `import` later in the same function | Module name fails at the top of the function | Move the import to the top of the file |
| 9 | `except ... as err` used after the block | Fails after the handler ends | Copy `err` to another variable |
| 10 | Built-in name like `len` reassigned later | Built-in suddenly "missing" | Pick a different variable name |

## 1. Augmented assignment (+=) to a global

This is the example from the top of the post, and it is the most common cause by far. Counters, running totals and "last seen" values all hit it. `+=`, `-=`, `*=` and friends all count as assignments, so the name becomes local.

The cleanest fix is to stop mutating the global at all: pass the value in and return the new one. If the function really must change module-level state, declare it with the [global statement](https://docs.python.org/3/reference/simple_stmts.html). Here are both, plus the fixes for causes 3, 4 and 6, from one test run:

```
# Fix 1: pass in and return instead of mutating a global
def add_visit(count):
    return count + 1

visits = 0
visits = add_visit(visits)
print("returned:", visits)

# Fix 2: global, when you really mean the module-level name
count = 0
def add_visit_global():
    global count
    count += 1
    return count
print("global:", add_visit_global(), add_visit_global())

# Fix 3: nonlocal for closures
def make_counter():
    n = 0
    def increment():
        nonlocal n
        n += 1
        return n
    return increment
c = make_counter()
print("nonlocal:", c(), c(), c())

# Fix 4: initialise before branches and loops
def label(score):
    result = "fail"
    if score >= 50:
        result = "pass"
    return result
print("label:", label(72), label(30))

def last_item(items, default=None):
    item = default
    for item in items:
        pass
    return item
print("last_item:", last_item(["a", "b"]), last_item([]))
```

```
returned: 1
global: 1 2
nonlocal: 1 2 3
label: pass fail
last_item: b None
```

I prefer the return-value version: it keeps functions easy to test.

## 2. Reading a global, then assigning it later

This one surprises people because the failing line is a plain read. The assignment that causes it sits further down:

```
total = 100

def report():
    print("total is", total)
    total = 0

report()
```

```
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c02_read_then_assign.py", line 7, in <module>
    report()
    ~~~~~~^^
  File "/workspace/seo-posts-nov08/test/python/c02_read_then_assign.py", line 4, in report
    print("total is", total)
                      ^^^^^
UnboundLocalError: cannot access local variable 'total' where it is not associated with a value
```

The traceback points at the `print`, not at `total = 0`. Whenever the caret lands on a harmless-looking read, scan the rest of the function for any assignment to the same name. Usually you meant a different variable, so rename the local (for example `new_total = 0`). If you really meant to reset the global, add `global total` as the first line of the function.

## 3. A variable assigned only inside an if branch

Here the function works for some inputs and crashes for others, which makes it easy to ship by accident:

```
def label(score):
    if score >= 50:
        result = "pass"
    return result

print(label(72))
print(label(30))
```

```
pass
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c03_if_branch.py", line 7, in <module>
    print(label(30))
          ~~~~~^^^^
  File "/workspace/seo-posts-nov08/test/python/c03_if_branch.py", line 4, in label
    return result
           ^^^^^^
UnboundLocalError: cannot access local variable 'result' where it is not associated with a value
```

A score of 72 takes the `if` branch and returns `"pass"`. A score of 30 skips the only assignment, so `result` never gets a value. The fix is to set a default before the branch (`result = "fail"`) or add an `else`. The fixes run above prints `label: pass fail`.

A close cousin of this bug is a function that only returns inside an `if`. That one does not raise UnboundLocalError. It quietly returns `None`, and the crash shows up later as ['NoneType' object is not subscriptable](https://mfaysal.com/blog/python-typeerror-nonetype-object-is-not-subscriptable).

## 4. Using a loop variable after an empty loop

A `for` loop assigns its target on every pass. If the iterable is empty, the loop body never runs and the name never gets a value:

```
def last_item(items):
    for item in items:
        pass
    return item

print(last_item(["a", "b"]))
print(last_item([]))
```

```
b
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c04_empty_loop.py", line 7, in <module>
    print(last_item([]))
          ~~~~~~~~~^^^^
  File "/workspace/seo-posts-nov08/test/python/c04_empty_loop.py", line 4, in last_item
    return item
           ^^^^
UnboundLocalError: cannot access local variable 'item' where it is not associated with a value
```

Two items work fine. An empty list crashes. Give the name a value before the loop, as `last_item(items, default=None)` does in the fixes run, or skip the loop entirely and use `items[-1] if items else None`. Empty lists cause plenty of other crashes too. If you also index into them, see [IndexError: list index out of range](https://mfaysal.com/blog/python-indexerror-list-index-out-of-range).

## 5. A try block that failed before the assignment

This is a nasty one, because the second error hides the first. `open()` fails, so `f` is never assigned, and then `finally` tries to close it:

```
def load(path):
    try:
        f = open(path)
        data = f.read()
    except FileNotFoundError:
        print("missing file:", path)
    finally:
        f.close()

load("does-not-exist.txt")
```

```
missing file: does-not-exist.txt
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c05_try_except.py", line 10, in <module>
    load("does-not-exist.txt")
    ~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/seo-posts-nov08/test/python/c05_try_except.py", line 8, in load
    f.close()
    ^
UnboundLocalError: cannot access local variable 'f' where it is not associated with a value
```

The handler ran and printed its message, then `f.close()` blew up. For files, sockets and locks, a `with` block solves this completely: it only closes what actually opened. When you cannot use `with`, set the variable to `None` before the `try` and check it in `finally`. Here are the `with` version and the fixes for causes 9 and 10:

```
# Fix: with closes the file only if it opened
def load(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        print("missing file:", path)
        return None
print("load:", load("does-not-exist.txt"))

# Fix: keep the exception in another name
def parse(value):
    last_error = None
    try:
        return int(value)
    except ValueError as err:
        last_error = err
    print("last error was:", repr(last_error))
parse("abc")

# Fix: rename instead of shadowing a builtin
def summary(words):
    print("count:", len(words))
    word_count = len(words)
    return word_count
print("summary:", summary(["hi", "there"]))
```

```
missing file: does-not-exist.txt
load: None
last error was: ValueError("invalid literal for int() with base 10: 'abc'")
count: 2
summary: 2
```

## 6. A nested function updating an outer variable

Closures follow the same rule. The inner function assigns `count`, so `count` is local to the inner function, even though the outer function already has one:

```
def make_counter():
    count = 0
    def increment():
        count += 1
        return count
    return increment

counter = make_counter()
print(counter())
```

```
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c06_nested.py", line 9, in <module>
    print(counter())
          ~~~~~~~^^
  File "/workspace/seo-posts-nov08/test/python/c06_nested.py", line 4, in increment
    count += 1
    ^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
```

The fix is `nonlocal`, which tells Python to bind the name in the nearest enclosing function scope. In the fixes run, the counter with `nonlocal n` printed `nonlocal: 1 2 3`. The official [Python programming FAQ](https://docs.python.org/3/faq/programming.html) covers this exact case under "Why am I getting an UnboundLocalError when the variable has a value?".

`global` would be wrong here: it points at the module, not at `make_counter`.

## 7. Reading a name after del

`del` unbinds a name. Inside a function, the name stays local, so reading it afterwards raises the same error:

```
def clean():
    temp = [1, 2, 3]
    del temp
    return len(temp)

clean()
```

```
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c07_del.py", line 6, in <module>
    clean()
    ~~~~~^^
  File "/workspace/seo-posts-nov08/test/python/c07_del.py", line 4, in clean
    return len(temp)
               ^^^^
UnboundLocalError: cannot access local variable 'temp' where it is not associated with a value
```

This usually appears after a refactor adds a `del` above an older use. Move the `del` after the last use, or drop it; locals are freed when the function returns anyway.

## 8. An import later in the same function

`import json` is an assignment too: it binds the name `json`. Put one inside a function, even in a branch that never runs, and the module-level import stops working for that whole function:

```
import json

def save(data):
    text = json.dumps(data)
    if not text:
        import json
    return text

print(save({"a": 1}))
```

```
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c08_late_import.py", line 9, in <module>
    print(save({"a": 1}))
          ~~~~^^^^^^^^^^
  File "/workspace/seo-posts-nov08/test/python/c08_late_import.py", line 4, in save
    text = json.dumps(data)
           ^^^^
UnboundLocalError: cannot access local variable 'json' where it is not associated with a value
```

The same thing happens with `from x import y` and with any name that matches a module you imported at the top. Keep imports at the top of the file, or put a lazy import at the very start of the function.

## 9. Using an except ... as variable after the block

Python deletes the name bound by `except ... as err` when the handler finishes. The [compound statements reference](https://docs.python.org/3/reference/compound_stmts.html) explains why: it breaks a reference cycle between the exception and the stack frame. Inside a function, reading `err` afterwards fails:

```
def parse(value):
    try:
        return int(value)
    except ValueError as err:
        print("bad value:", value)
    print("last error was:", err)

parse("abc")
```

```
bad value: abc
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c09_except_as.py", line 8, in <module>
    parse("abc")
    ~~~~~^^^^^^^
  File "/workspace/seo-posts-nov08/test/python/c09_except_as.py", line 6, in parse
    print("last error was:", err)
                             ^^^
UnboundLocalError: cannot access local variable 'err' where it is not associated with a value
```

At module level the same code raises `NameError` instead. Either way, copy the exception to another variable inside the handler (`last_error = err`), as the fixes run above shows. It printed `last error was: ValueError("invalid literal for int() with base 10: 'abc'")`.

## 10. Shadowing a built-in like len

Built-ins follow the rule as well. Assign to `len`, `list`, `id`, `type` or `sum` anywhere in a function, and every earlier use of that built-in in the function breaks:

```
def summary(words):
    print("count:", len(words))
    len = 3
    return len

summary(["hi", "there"])
```

```
Traceback (most recent call last):
  File "/workspace/seo-posts-nov08/test/python/c10_shadow_builtin.py", line 6, in <module>
    summary(["hi", "there"])
    ~~~~~~~^^^^^^^^^^^^^^^^^
  File "/workspace/seo-posts-nov08/test/python/c10_shadow_builtin.py", line 2, in summary
    print("count:", len(words))
                    ^^^
UnboundLocalError: cannot access local variable 'len' where it is not associated with a value
```

The error says "local variable 'len'", which looks bizarre until you spot the assignment two lines down. Rename the variable (`word_count`) and the built-in works again. If you are cleaning up text or JSON while you debug, my [free browser tools](https://mfaysal.com/free-tools/) run entirely on your device. Pylint (redefined-builtin) and Ruff with its flake8-builtins rules flag shadowed built-ins before you run the code.

## How to find the cause in under a minute

1. Read the last line of the traceback and note the variable name.
2. Search the *whole* function for that name followed by `=`, `+=`, `for`, `import`, `del`, `as` or a walrus `:=`.
3. If the assignment is meant to change an outer variable, add `global` (module) or `nonlocal` (enclosing function).
4. If it is a different value with the same name, rename it.
5. If it only happens on some inputs, look for an `if`, loop or `try` that can skip the assignment, and give the variable a default first.
6. Still stuck? Print `your_function.__code__.co_varnames`, or use the [symtable module](https://docs.python.org/3/library/symtable.html) as in the diagnosis script above.

## Frequently asked questions

### What does UnboundLocalError: cannot access local variable mean?

Python decided at compile time that the name is local to the function, because something in the function assigns to it. Then a line read the name before that assignment had run, so the local had no value.

### Is it the same as local variable referenced before assignment?

Yes. Python 3.10 and older print local variable 'x' referenced before assignment. Python 3.11 and newer print cannot access local variable 'x' where it is not associated with a value. In my tests the cause and fixes were identical.

### Why does the error happen when the variable is defined at the top of the file?

Any assignment to the same name inside the function, including +=, makes it a separate local variable for the whole function. The module-level variable is ignored until you declare it with global.

### Should I just use global to fix it?

Use global only when the function genuinely must change module-level state. In most code it is cleaner to pass the value in as an argument and return the new value.

### What is the difference between global and nonlocal?

global binds the name at module level. nonlocal binds it in the nearest enclosing function, which is what you need for counters inside closures and decorators.

### Is UnboundLocalError a subclass of NameError?

Yes. issubclass(UnboundLocalError, NameError) returned True in my test, so except NameError catches both. Fixing the scope is still better than catching the error.

*Last checked: 8 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.*

---

*Originally published on [mfaysal.com](https://mfaysal.com/blog/python-unboundlocalerror-cannot-access-local-variable).*

I'm learning cybersecurity on TryHackMe — follow my progress at [tryhackme.com/p/mfaysal.dev](https://tryhackme.com/p/mfaysal.dev).

