Python
Error Handling
UnboundLocalError
Programming
Debugging

UnboundLocalError local variable 'batch_outputs' referenced before assignment

Master System Design with Codemia

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

Introduction

UnboundLocalError: local variable 'x' referenced before assignment occurs when Python detects that a variable is assigned somewhere in a function and therefore treats it as local for the entire function, even before the assignment line executes. If you try to read the variable before the assignment (often because the assignment is inside an if block that did not execute), Python raises this error instead of looking at the outer scope. The fix depends on whether you intended to use a local or global variable.

The Error

python
1batch_outputs = []
2
3def process_batch(data):
4    if data:
5        batch_outputs = transform(data)  # Assignment makes it local
6    return batch_outputs  # UnboundLocalError if data is falsy
7
8process_batch([])  # Crashes

Python sees batch_outputs = transform(data) and marks batch_outputs as a local variable for the entire function. When data is empty, the assignment never runs, but the return still tries to read the local (unset) variable.

Why Python Does This

Python determines variable scope at compile time, not runtime. If a variable name appears on the left side of an assignment anywhere in a function, it is local to the entire function:

python
1x = 10
2
3def foo():
4    print(x)  # UnboundLocalError — x is local because of line below
5    x = 20    # This makes x local for the ENTIRE function
6
7foo()

Even though print(x) comes before x = 20, Python has already decided that x is local based on the assignment. The print tries to read local x before it has been assigned.

Fix 1: Initialize the Variable Before the Condition

python
1def process_batch(data):
2    batch_outputs = []  # Initialize before conditional assignment
3    if data:
4        batch_outputs = transform(data)
5    return batch_outputs  # Always has a value

This ensures batch_outputs is always assigned before it is read, regardless of which branch executes.

Fix 2: Use the global Keyword

If you intend to modify a module-level variable:

python
1batch_outputs = []
2
3def process_batch(data):
4    global batch_outputs  # Tell Python this is the module-level variable
5    if data:
6        batch_outputs = transform(data)
7    return batch_outputs

global tells Python to use the module-level batch_outputs instead of creating a local one. Use this sparingly — global mutable state makes code harder to test and debug.

Fix 3: Use the nonlocal Keyword (Nested Functions)

For variables in an enclosing function scope:

python
1def outer():
2    count = 0
3
4    def inner():
5        nonlocal count  # Refers to outer's count
6        count += 1      # Without nonlocal, this is UnboundLocalError
7        return count
8
9    return inner()

nonlocal tells Python that count belongs to the enclosing scope, not the local scope. Without it, count += 1 (which is count = count + 1) makes count local, and reading count on the right side fails.

Fix 4: Use a Return Value Instead of Mutation

python
1# Instead of modifying a variable conditionally:
2def process_batch(data):
3    if data:
4        return transform(data)
5    return []  # Explicit default
6
7# Cleaner: no ambiguity about variable scope
8result = process_batch(my_data)

Common Patterns That Trigger This Error

Augmented Assignment on Global

python
1counter = 0
2
3def increment():
4    counter += 1  # UnboundLocalError
5    # counter += 1 is counter = counter + 1
6    # Python sees assignment → local, reads counter → not assigned yet
7
8# Fix:
9def increment():
10    global counter
11    counter += 1

Loop Variable Leaking Expectation

python
1def find_item(items):
2    for item in items:
3        if item.matches():
4            found = item
5            break
6    return found  # UnboundLocalError if no item matches
7
8# Fix: initialize before loop
9def find_item(items):
10    found = None
11    for item in items:
12        if item.matches():
13            found = item
14            break
15    return found

Try/Except Variable Scope

python
1def parse_number(s):
2    try:
3        result = int(s)
4    except ValueError:
5        print("Invalid number")
6    return result  # UnboundLocalError if exception occurred
7
8# Fix:
9def parse_number(s):
10    result = None
11    try:
12        result = int(s)
13    except ValueError:
14        print("Invalid number")
15    return result

How to Debug

python
1# Check where the variable is assigned in the function
2import dis
3
4def example():
5    if False:
6        x = 10
7    return x
8
9dis.dis(example)
10# The bytecode shows LOAD_FAST for x (local variable load)
11# instead of LOAD_GLOBAL — confirming Python treats x as local

Common Pitfalls

  • Assignment inside if/try/for blocks: Variables assigned inside conditional blocks are still local to the function, but they may not be assigned if the block does not execute. Always initialize before the block.
  • global overuse: Using global everywhere to avoid the error introduces shared mutable state. Prefer passing values as arguments and returning results.
  • Augmented assignment (+=, -=): x += 1 is x = x + 1 — it both reads and writes. If x is not local yet, the read fails. Use global or nonlocal to reference the outer variable.
  • Variable name shadowing: Using the same name for a local and global variable is confusing. Rename the local variable to avoid ambiguity: local_outputs instead of reusing batch_outputs.
  • Python 2 vs Python 3: Python 2 does not have nonlocal. The workaround was using mutable containers: count = [0]; count[0] += 1 (modifying the container does not create a new local).

Summary

  • UnboundLocalError means Python treats a variable as local (because it is assigned somewhere in the function) but it was read before being assigned
  • Python determines scope at compile time based on the presence of assignments, not at runtime
  • Initialize variables before conditional/try blocks to ensure they always have a value
  • Use global for module-level variables or nonlocal for enclosing function variables
  • Prefer returning values over modifying outer-scope variables to avoid scope confusion

Course illustration
Course illustration

All Rights Reserved.