Mastering Python's LEGB Rule: A Comprehensive Guide to Variable Scope
Introduction to Python Variable Scope
When you write Python code, every variable and function name exists within a specific scope—the region of your program where that name is accessible. Understanding how Python resolves names is crucial for avoiding bugs and writing clean, maintainable code. The LEGB rule provides a systematic way to trace this resolution process, moving from the innermost scope outward. In this article, you will explore what each letter of LEGB means, see real‑world examples, and learn how to cross scope boundaries using the global and nonlocal statements.

By the end, you will be able to predict how Python resolves any name reference in your code—whether it's a variable inside a function, a nested function, or a built‑in like len(). Let's dive into the scope hierarchy.
What Is the LEGB Rule?
LEGB stands for Local, Enclosing, Global, Built‑in. It defines the order Python follows when looking up a name:
- Local – inside the current function or lambda
- Enclosing – in any enclosing function(s) (for nested functions)
- Global – at the module (file) level
- Built‑in – from the set of names pre‑defined in Python (e.g.,
print,len)
Python searches these scopes in this exact order. If the name is not found in any of them, a NameError is raised.
Local Scope
The local scope is the innermost scope. It is created whenever a function or lambda is called. Variables defined inside a function are local to that function and cannot be accessed from outside. For example:
def my_function():
x = 10 # local to my_function
print(x) # works
my_function()
# print(x) # NameError
Every function call creates a new local scope. This means recursive calls have separate local namespaces.
Enclosing Scope
The enclosing scope comes into play with nested functions (a function defined inside another function). When Python looks up a name inside the inner function, it first checks the local scope of that inner function, then the local scope of the outer (enclosing) function, and so on. This is also known as non‑local scope.
def outer():
y = 20
def inner():
print(y) # looks in outer's local scope
inner()
outer()
Note that Python treats y as a closed‑over variable (also called a closure) if the inner function refers to it.
Global Scope
The global scope is the top‑level scope of your module. Variables defined outside any function or class are global. They are accessible from anywhere in the module, but you must use the global keyword to reassign them inside a function (otherwise Python treats them as local).
z = 30 # global
def my_function():
global z
z = 40 # now modifies the global z
my_function()
print(z) # 40
Global variables can make code harder to reason about, so use them sparingly.
Built‑in Scope
The built‑in scope is the outermost scope. It contains names like print, len, range, and TypeError. Python always looks in the built‑in scope last. You can inspect built‑ins with dir(__builtins__). Be careful not to shadow built‑ins by using the same name for your own variables—this can lead to confusing bugs.
Crossing Scope Boundaries: global and nonlocal
Sometimes you need to modify a variable that belongs to an outer scope. Python provides two statements for this:

global– tells Python that a variable inside a function refers to a global variable (even for assignment).nonlocal– used inside nested functions to indicate that a variable refers to a variable in the nearest enclosing function (excluding globals).
Without these statements, assigning to a variable inside a function creates a new local variable, leaving the outer variable unchanged.
Example: Using global
counter = 0
def increment():
global counter
counter += 1
increment()
increment()
print(counter) # 2
Example: Using nonlocal
def outer():
x = 10
def inner():
nonlocal x
x = 20
inner()
print(x) # 20
outer()
Practical Examples of the LEGB Rule in Action
Consider this nested function that uses all scopes:
import math # part of built‑in? No, math is not built‑in; it's a module in global scope.
PI = 3.14159 # global
def area_calculator(radius):
# local variable 'radius'
def inner():
# uses nonlocal 'radius' (enclosing), global 'PI', and built‑in 'print'
print(f"Area: {PI * radius**2}")
inner()
area_calculator(2)
Here, Python resolves print from built‑ins, PI from globals, radius from the enclosing scope of area_calculator. No local variable shadows anything.
A common pitfall is forgetting the global or nonlocal statement when trying to reassign an outer variable. For example:
count = 0
def increment():
# missing 'global count'
count += 1 # Raises UnboundLocalError
increment()
Because Python sees an assignment and assumes count is local—but it hasn't been defined locally. The remedy is to add global count.
Conclusion
Understanding the LEGB rule is essential for every Python developer. It clarifies how variable names are resolved and helps you debug scope‑related errors. Remember the order: Local → Enclosing → Global → Built‑in. Use global and nonlocal judiciously to modify outer scopes, and always consider whether a variable should be local, global, or enclosed.
Now that you've seen the theory and practice, you can confidently navigate Python's scoping rules in your projects. Happy coding!
Related Articles
- Empower Your AI Agent to Autonomously Deploy Cloud Apps: A Step-by-Step Guide
- NVIDIA's Nemotron 3 Nano Omni: A Unified Multimodal Model for Faster, Cheaper AI Agents
- Go Developers Cry for Help: 2025 Survey Reveals Critical Gaps in Documentation, AI Tooling, and Language Evolution
- 7 Essential Insights into Python 3.15 Alpha 4: What Developers Need to Know
- Go 1.26's Source-Level Inliner: A Self-Service Modernization Tool
- 7 Key Updates About the Python Insider Blog Migration
- Crafting the Perfect Programming Question: Your Self-Help Debugging Guide
- Mastering Asynchronous Node.js: From Callbacks to Promises