Python Scope & LEGB Rule: Mastering Variable Visibility

Python LEGB Rule: Quick Answer
Python resolves variable names using the LEGB rule: it searches Local scope first, then Enclosing (outer function) scope, then Global (module-level) scope, and finally Built-in scope. The first match wins. Understanding this lookup order prevents NameError bugs and helps you write clean, modular code.
Introduction to Python Scope
In programming, Scope refers to the region of a program where a specific variable is accessible. Simply put, it determines the "visibility" of your variables and functions.
Understanding scope is crucial for avoiding Name Collisions (where two different parts of your code use the same name for different purposes) and for writing clean, modular code.
What is a Namespace?
Before diving into scope, we must understand Namespaces. A namespace is essentially a dictionary where keys are variable names and values are the objects themselves.
Python uses namespaces to keep track of variables in different areas of your code. For example, the os module has its own namespace, allowing you to have a variable named getcwd in your script without conflicting with os.getcwd().
The LEGB Rule: Python’s Lookup Strategy
When you reference a variable, Python searches for it in a specific order. This is known as the LEGB Rule:
- L: Local Scope — Inside the current function or lambda.
- E: Enclosing Scope — Inside any enclosing (outer) functions.
- G: Global Scope — At the top level of the script or module.
- B: Built-in Scope — Reserved keywords and built-in functions like
len,int,print.
1. Local (Function) Scope
Variables defined inside a function belong to that function’s local scope. They are created when the function is called and destroyed when it returns.
2. Enclosing (Nonlocal) Scope
This occurs in nested functions. The "outer" function’s local scope becomes the "enclosing" scope for the "inner" function.
3. Global (Module) Scope
The global scope is the top-most level of your Python script. Variables defined here are visible throughout the entire file.
4. Built-in Scope
This is the widest scope, containing all of Python’s built-in objects. It’s always available.
Avoid Shadowing
Never name your variables after built-in functions (e.g., `list = [1, 2]`). Doing so 'shadows' the built-in name, making the original `list()` function inaccessible in that scope!
Modifying Scope: global & nonlocal
Normally, you can read variables from outer scopes, but you cannot modify them. To do so, you need special keywords.
The global Keyword
Used to modify a variable defined at the top level of your script from within a function.
The nonlocal Keyword
Used in nested functions to modify a variable in the enclosing (outer) function.
Python Scope Cheat Sheet
| Operation | Local Code | Enclosed Code | Global Code | Built-in |
|---|---|---|---|---|
| Access Local | ✓ | ✘ | ✘ | ✘ |
| Modify Local | ✓ | ✘ | ✘ | ✘ |
| Access Enclosing | ✓ | ✓ | ✘ | ✘ |
| Modify Enclosing | nonlocal | ✓ | ✘ | ✘ |
| Access Global | ✓ | ✓ | ✓ | ✘ |
| Modify Global | global | global | ✓ | ✘ |
| Access Built-in | ✓ | ✓ | ✓ | ✓ |
LEGB in Action: A Complete Example
Here is a single example that demonstrates all four scopes simultaneously:
Output:
Each scope is completely isolated unless you explicitly use global or nonlocal.
Common Scope Mistakes (And How to Fix Them)
Mistake 1: UnboundLocalError
As soon as Python sees count += 1 inside a function, it assumes count is a local variable — even before the assignment. This is why you get UnboundLocalError.
Mistake 2: Shadowing Built-ins
Never name your variables after Python built-ins like list, dict, str, len, id, type, or input.
Closures: Practical Use of Enclosing Scope
A closure is an inner function that "closes over" variables from its enclosing scope, preserving them even after the outer function has returned:
Closures are the foundation of decorators in Python — a powerful pattern for modifying function behaviour.
Related Python Topics
- Python Functions and Parameters — scope applies inside every function you write
- Python OOP Overview — class scope is a fourth context where LEGB applies
- Python Class Methods and Attributes —
selfandclsare always local to their method - Python Loops and Iterations — loop variables have function scope, not block scope
For the official language specification, see Python Naming and Binding documentation and the global statement.
Conclusion
Mastering the LEGB rule and knowing when to use global or nonlocal is a hallmark of an intermediate Python developer. It allows you to build complex, nested architectures without losing track of your data!
Up Next
Ready for more? Let's explore Python Inheritance, where we apply these scoping rules to Class hierarchies.
Common Mistakes with Python Scope and LEGB
1. Modifying a global variable without the global keyword
Assigning to a name inside a function creates a new local variable — it does not modify the global. If your function reads a global without assigning to it, global is unnecessary. If it assigns to it, declare global counter before the assignment. Forgetting this produces an UnboundLocalError when the function tries to read the variable before the local assignment is reached. See the Python scope documentation.
2. Overusing global state
Functions that rely on global variables are harder to test and reason about because their behaviour depends on external state that may change at any point. Prefer passing values as arguments and returning results explicitly. Reserve module-level globals for true constants (all-caps by convention: MAX_RETRIES = 3).
3. Confusing nonlocal and global
nonlocal reaches into the nearest enclosing function scope (one level up in nested functions). global reaches all the way to the module level. Using global when you meant nonlocal in a nested function silently creates or modifies a module-level variable instead of the enclosing function's variable. Always pick the narrowest scope that solves the problem.
4. Variable shadowing built-ins
Naming a variable list, dict, input, id, or type shadows the built-in with the same name in every line of code that follows within that scope. The original built-in becomes inaccessible without using builtins.list. Python will not warn you about this. Use descriptive names (user_list, config_dict) instead of single-word names that collide with built-ins. The builtins module lists every name at risk.
5. Loop variable leaking into enclosing scope
In Python, for loops do not create their own scope. After for i in range(5):, the variable i remains accessible in the enclosing scope with the value from the last iteration. This is intentional but surprises developers from C, Java, or JavaScript backgrounds. Be explicit if you need a loop variable to remain: assign it before the loop or use a separate name.
Frequently Asked Questions
What is the LEGB rule in Python?
LEGB stands for Local, Enclosing, Global, and Built-in — the four scopes Python searches in order when resolving a name. Local is the current function body. Enclosing covers any outer functions in a closure. Global is the module level. Built-in is the builtins module containing names like print, len, and range. Python searches each scope in order and uses the first match it finds. The Python execution model documentation describes the full scoping rules.
When should I use nonlocal in Python?
Use nonlocal inside a nested function when you need to assign to a variable defined in the enclosing function's scope. The classic use case is a closure that accumulates state across multiple calls: a counter or accumulator function where the inner function must modify the outer function's variable. If you only read the outer variable (without assigning to it), nonlocal is not required. See PEP 3104 — Access to Names in Outer Scopes for the design rationale.
Why does Python's for loop variable persist after the loop ends?
Python's design philosophy keeps the language simple — adding block scope for for loops would require a new scoping rule distinct from function scope. The trade-off is that loop variables persist, which is occasionally useful (the last matched value in a search loop) but surprising to developers from block-scoped languages. If you want loop variable isolation, wrap the loop in a function or use a comprehension ([x for x in items] does not leak x in Python 3). The Python FAQ on scoping addresses this directly.
