Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected

Python

You Thought You Knew Python Scope? Let's Clear Up the Confusion Once and For All

Understand Python's LEGB scope rule, why UnboundLocalError happens, and how to correctly use global and nonlocal keywords to control variable visibility and assignment.

June 2026 · 5 min read · 1 views · 0 hearts

You Thought You Knew Python Scope? Let's Clear Up the Confusion Once and For All

If you've ever been bitten by a UnboundLocalError or wondered why a variable inside a function suddenly "forgot" its global value, you're not alone. Python's scope rules are elegant once you understand them, but they can feel like a trap until you do.

Let's walk through how Python decides which variable you're talking about—and why sometimes it's not the one you think.

The LEGB Rule: Your Mental Map

Python looks up variable names in a strict order: Local, Enclosing, Global, Built-in. Think of it as a set of nested boxes.

  • Local: inside the current function
  • Enclosing: any outer function that wraps the current one (for nested functions)
  • Global: the top level of the module (your script file)
  • Built-in: Python's predefined names like print, len, range
x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)  # local

    inner()
    print(x)  # enclosing

outer()
print(x)  # global

Each x is a separate variable. Python finds the closest match in the LEGB chain and stops.

The Trap: Assignment Makes a Variable Local (Unless You Say Otherwise)

Here's where most people get burned:

count = 0

def increment():
    count += 1  # UnboundLocalError!

increment()

Why does this crash? Because count += 1 is an assignment. Python sees an assignment inside the function and treats count as a local variable—before it ever sees the += operation. The local count hasn't been given a value yet, so it raises UnboundLocalError.

The fix is the global keyword:

count = 0

def increment():
    global count
    count += 1

increment()
print(count)  # 1

Nested Functions Need nonlocal, Not global

When you're inside a nested function and want to modify a variable from an outer (but not global) scope, global won't help. You need nonlocal:

def outer():
    x = "outer value"

    def inner():
        nonlocal x
        x = "changed by inner"

    inner()
    print(x)  # "changed by inner"

outer()

Without nonlocal, the assignment x = "changed by inner" would create a new local variable inside inner, leaving the outer x untouched.

When You Can Read But Not Write—Unless It's Mutable

Here's a subtle twist: you can read a global variable inside a function without saying global:

greeting = "Hello"

def say_hello():
    print(greeting)  # Works fine, reads global

say_hello()  # Hello

But if you assign to it, you need global. However, there's a workaround for mutable objects like lists or dicts:

items = []

def add_item(item):
    items.append(item)  # No error! Modifying, not reassigning

add_item("apple")
print(items)  # ['apple']

Why does this work? Because items.append doesn't rebind the name items—it mutates the list object that items points to. The variable items is still the global one. You're only modifying, not reassigning.

The Built-in Scope: Don't Shadow It

Python's built-in names are always available, but you can accidentally override them:

def my_print():
    print = "I'm a string now"
    print("Hello")  # TypeError!

my_print()

Once you assign to print locally, the built-in print function is hidden for the rest of that scope. A good practice: never use built-in names for your own variables.

Quick Cheat Sheet

Situation What to use
Read a global variable Nothing needed
Assign to a global variable global variable_name
Assign to a variable in an enclosing function nonlocal variable_name
Modify a mutable global (list, dict, set) Nothing needed (but careful)
Avoid name clashes with built-ins Choose different variable names

The One-Liner That Explains It All

Python's scoping is lexical (determined at compile time), and assignment binds a name to the local scope by default.

Run with that understanding, and you'll stop chasing ghost bugs. The LEGB rule and the global/nonlocal keywords are your tools—not your enemies. Use them intentionally, and your code will be cleaner, clearer, and much less surprising.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.