Python Scope & LEGB Rule

๐Ÿ Python 3.12+ ๐ŸŸข Chapter 17 of 65 ๐Ÿ“‚ Phase 4: Functions & Reusable Code ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: LEGB Hierarchy ยท Local vs Global ยท global & nonlocal ยท Closures ยท Pure Functions ยท Converters Project
Master variable scope resolution in Python: the LEGB lookup rule, local vs global namespaces, modifying outer scope with global and nonlocal, closures, pure functions, and building currency/unit converters.
1The LEGB Scope Resolution Hierarchy

In Python, the scope of a variable determines where in your program that name is visible and accessible. When you reference a variable name, Python searches four nested namespaces in a strict sequence known as the LEGB Rule:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ L - LOCAL: Defined inside current function (def) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ E - ENCLOSING: Defined in outer/parent function โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ G - GLOBAL: Defined at top-level of module (.py file) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ B - BUILT-IN: Preloaded Python builtins (len, print) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Python stops searching as soon as it finds the first matching variable name in the LEGB hierarchy. If the name is not found in any of the four scopes, a NameError is raised.

๐Ÿ’ป Example 1: LEGB Scope Resolution Hierarchy in Action
# Demonstrating the LEGB scope hierarchy:
global_var = "๐ŸŒ I am GLOBAL"

def outer_function():
    enclosing_var = "๐Ÿ“ฆ I am ENCLOSING (Outer Function)"
    
    def inner_function():
        local_var = "๐Ÿ  I am LOCAL (Inner Function)"
        print("Inside inner function:")
        print("  1.", local_var)      # Local Scope (L)
        print("  2.", enclosing_var)  # Enclosing Scope (E)
        print("  3.", global_var)     # Global Scope (G)
        print("  4. Built-in len:", len("Test")) # Built-in Scope (B)
        
    inner_function()

outer_function()
๐Ÿ” Scope Lookup Sequence:

inner_function accesses its own local variable (L), its parent's enclosing variable (E), module-level global variable (G), and built-in function len() (B) smoothly.

2The global Keyword & Modifying Global State

By default, if you assign to a variable inside a function (x = 100), Python creates a brand new Local variable, even if a global variable named x already exists! To rebind a global variable from inside a function, declare it with global:

๐Ÿ’ป Example 2: Modifying Global Variables with "global"
counter = 0  # Global variable

def increment_bad():
    # Attempting counter += 1 without 'global' raises UnboundLocalError!
    # Because assignment makes 'counter' local before reading it!
    pass

def increment_safe():
    global counter  # Explicitly declares intent to rebind the global variable
    counter += 1
    print("Global counter incremented to:", counter)

increment_safe()
increment_safe()
print("Final Global Counter:", counter)
๐Ÿ” Best Practice Note:

Avoid overusing global variables in production code because global state makes programs difficult to debug and test in parallel.

3Closures & The nonlocal Keyword

When a nested inner function references variables from its enclosing function, it forms a Closure. To rebind an enclosing variable from the inner function, use the nonlocal keyword:

๐Ÿ’ป Example 3: Function Closures and the "nonlocal" Keyword
def create_bank_account(initial_balance):
    balance = initial_balance  # Enclosing variable
    
    def deposit(amount):
        nonlocal balance  # Rebinds enclosing balance!
        balance += amount
        return f"Deposited Rs.{amount} | Current Balance: Rs.{balance}"
        
    return deposit

# Create an isolated account closure:
my_account = create_bank_account(1000)
print(my_account(500))
print(my_account(250))
๐Ÿ” Closure Encapsulation:

balance remains alive in memory attached to my_account, providing private state encapsulation without writing a full class!

4Pure Functions vs Side Effects

A Pure Function is a function that:

  1. Given the same arguments, ALWAYS returns the exact same result (Deterministic).
  2. Produces zero side effects (does not mutate global state, modify passed lists in-place, or write to external files).
๐Ÿ’ป Example 4: Impure vs Pure Functions
# โŒ IMPURE FUNCTION (Mutates external global state):
total_sales = 0
def add_sale_impure(amount):
    global total_sales
    total_sales += amount
    return total_sales

# โœ… PURE FUNCTION (Deterministic, zero side-effects):
def calculate_sale_pure(current_total, new_amount):
    return current_total + new_amount

print("Pure Result 1:", calculate_sale_pure(100, 50)) # 150
print("Pure Result 2:", calculate_sale_pure(100, 50)) # Always 150!
๐Ÿ” Reliability:

Pure functions are effortless to unit test, cache (memoization), and execute concurrently across multiple CPU threads.

5Practical Project 3: Dynamic Currency & Unit Converter Engine

A pure, modular converter engine supporting currency exchange and metric units:

๐Ÿ’ป Example 5: Currency and Unit Converter Project
# Dynamic Currency & Unit Converter Engine

# Currency Rates relative to 1 USD (Base)
EXCHANGE_RATES = {
    "USD": 1.00,
    "INR": 86.50,
    "EUR": 0.92,
    "GBP": 0.79,
    "JPY": 152.00
}

def convert_currency(amount, from_curr, to_curr):
    """Convert currency using pure conversion arithmetic."""
    from_rate = EXCHANGE_RATES.get(from_curr.upper())
    to_rate = EXCHANGE_RATES.get(to_curr.upper())
    
    if not from_rate or not to_rate:
        return None, "โŒ Invalid Currency Code!"
        
    # Convert from source to USD base, then to target currency
    amount_in_usd = amount / from_rate
    converted_amount = amount_in_usd * to_rate
    return round(converted_amount, 2), f"1 {from_curr} = {to_rate/from_rate:.4f} {to_curr}"

# Test Currency Conversions:
amt_inr, rate_info = convert_currency(100, "USD", "INR")
print(f"๐Ÿ’ฒ 100 USD = Rs.{amt_inr} INR ({rate_info})")

amt_eur, rate_info2 = convert_currency(5000, "INR", "EUR")
print(f"๐Ÿ’ถ 5000 INR = โ‚ฌ{amt_eur} EUR ({rate_info2})")
๐Ÿ” Architecture Breakdown:

By converting first to a common base (USD), we can convert between any arbitrary pair of currencies with just $N$ stored exchange rates instead of $N^2$ pairs!

โš ๏ธ Common Developer Pitfall: UnboundLocalError: local variable referenced before assignment

If you read a global variable and then assign to it in the same function without declaring "global x", Python flags the variable as local across the ENTIRE function body, raising UnboundLocalError when reading it on earlier lines.

๐Ÿ’ป Hands-on Interactive Practice Challenge

Create a unit converter function convert_distance(value, from_unit, to_unit) supporting "km", "miles", and "meters".

Python 3 Practice Challenge โ–ถ Run in Compiler
def convert_distance(val, from_unit, to_unit):
    to_meters = {"km": 1000, "meters": 1, "miles": 1609.34}
    
    meters = val * to_meters[from_unit]
    result = meters / to_meters[to_unit]
    return round(result, 2)

print("5 km in miles:", convert_distance(5, "km", "miles"), "miles")
print("10 miles in km:", convert_distance(10, "miles", "km"), "km")
Run This Challenge in Online Python IDE โ†’
โ“ Frequently Asked Questions (FAQ)

Q What is the LEGB rule in Python?

LEGB stands for Local, Enclosing, Global, Built-in. It defines the exact 4-step hierarchy Python uses to resolve variable names.

Q What is the difference between global and nonlocal?

global binds a variable name to the top-level module scope. nonlocal binds a variable name to the nearest enclosing parent function scope in nested functions.

Q Why are pure functions preferred in modern software development?

Pure functions produce no side effects and always return identical outputs for identical inputs, making them deterministic, bug-resistant, and easy to test and parallelize.

OC
Written by Our Compiler Technical Editorial Team
Reviewed for accuracy & tested on Python 3.12+ runtime ยท Last updated August 2026