October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk5 min

Python’s ‘UnboundLocalError’: It’s Not a Missing Variable, It’s Scope Decided in Advance

Python raises UnboundLocalError when a function binds a name anywhere in its body, making it local throughout. Learn the whole-block rule, the fixes with global, nonlocal, and local initialization, and how to troubleshoot.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

An UnboundLocalError usually does not mean the variable is missing. It means Python has already decided, for the entire function body, that the name is local to that function, and the line that fails reads the name before any value has been bound to that local. The module-level value you can see is never consulted.

What the error means

UnboundLocalError is a subclass of NameError. The built-in exceptions reference (Python 3.12 documentation) defines it as the error raised when a local name is referenced in a function or method but no value has been bound to it at that point. The difference from a plain NameError matters: a plain NameError means Python could not find the name anywhere it looked, while UnboundLocalError means Python found that the name belongs to the function’s local scope and had nothing to give it yet.

Why a later assignment breaks an earlier read

Python decides whether a name is local when it compiles the function, not when the line runs. The Python Language Reference, in the “Resolution of names” section of the execution model (Python 3.14 documentation), states the rule directly: if a name binding operation occurs anywhere within a code block, all uses of the name within that block are treated as references to the current block. Reading a name earlier in the function does not change this.

The Python FAQ (Programming FAQ, Python 3.14 documentation) gives the canonical example:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
x = 10

def foo():
    print(x)
    x += 1

Calling foo() raises UnboundLocalError. The augmented assignment x += 1 is a binding operation, so x is local throughout foo. The print(x) on the first line tries to read that local before it has a value, even though the module already bound a global x to 10. A function that only printed x, with no assignment anywhere in its body, would read the module value without trouble.

Which statements make a name local

A name is local to a function if the function binds it anywhere, and binding is broader than a plain =. The execution model enumerates these forms, and each one counts:

  • Plain and augmented assignment: x = …, x += …
  • Function parameters
  • Nested def and class statements, which bind the defined name
  • import statements, including import x as y and from m import x
  • Loop targets in for statements and targets in with … as clauses
  • Names bound by except … as (which Python also deletes when the clause ends)

This is why the traceback line is rarely where the fix belongs. The binding that caused the problem may sit several lines below the failing read, or inside a branch that never runs.

A conditional binding produces the same error on the paths that skip it:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def report(flag):
    if flag:
        total = 1
    return total   # UnboundLocalError when flag is false

The name is local because of the assignment inside the if. When flag is false, the return reads an unbound local.

Fixing the error

The right fix depends on which binding the function is meant to use. Make that decision first, then change the code to match.

Update a module-level variable: declare global

If the function is meant to read and rebind the module-level name, add a global declaration before any use of the name in the function. Python will then treat every reference in that function as the module binding. Placing the declaration after the first use is a compile-time SyntaxError, so it belongs at the top of the function.

x = 10

def foo():
    global x
    print(x)   # 10
    x += 1     # updates the module-level x

Update an enclosing function’s variable: declare nonlocal

In a nested function, nonlocal x selects the existing binding of x in the nearest enclosing function. The execution model requires that such a binding exist; if no enclosing function binds the name, Python rejects the code at compile time. Use nonlocal only when rebinding the outer variable is the actual intent. For simply reading an enclosing variable, no declaration is needed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a local value: bind it before the read

If the function should work with its own variable, give that variable a value before the first read, and make sure every path to the read binds it. For the report example, one fix is to initialize on every path:

def report(flag):
    total = 0
    if flag:
        total = 1
    return total

Initializing at the top also removes the error when the code is later restructured, which is a common source of this bug in long functions.

Mutating an object does not rebind the name

Calling a method on an object does not bind the name, so it never triggers this error. The distinction is between changing an object and assigning to the name:

items = []

def add(value):
    items.append(value)   # works: items is only read

def add_bad(value):
    items += [value]      # UnboundLocalError: += rebinds items

Both functions appear to change items, but only the second assigns to the name. The first needs no declaration. The second either needs global items or should use items.extend([value]), which mutates the list in place.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Intended behavior Appropriate change
Use or rebind a variable local to this function Bind it before the first read on every path
Rebind a module-level variable Declare global at the top of the function
Rebind a variable in an enclosing function Declare nonlocal in the nested function
Change an object’s contents without rebinding the name Call a mutating method; no declaration needed
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A troubleshooting sequence

  1. Search the whole function, not just the traceback line, for every binding of the failing name, including imports, loop and with targets, and nested definitions.
  2. Decide which binding the code should use: local, module-level, or enclosing-function.
  3. If the intent is an outer binding, add global or nonlocal at the top of the function.
  4. If the intent is a local binding, initialize the variable before any read and check each branch that leads to the read.
  5. Re-run the function along the path that failed. If the error persists, look for a second binding of the same name that you did not expect.

How it differs from related errors

  • Plain NameError means the name was not found in any scope Python searched. Check spelling, imports, and whether the name is defined before use at module level.
  • Closures normally read free variables from enclosing functions without any declaration. Only rebinding requires nonlocal.
  • Class bodies do not act as an enclosing scope for methods. A method cannot read a name defined in the class body as if it were a local of the surrounding code, so a name that looks shared with the class may still be resolved as global or unbound inside the method.

When the traceback names a variable that clearly exists elsewhere in the file, the cause is almost always a binding inside the function itself, and the search in the sequence above will find it.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.