Scope
Learn how Python finds variable names using local, global, nonlocal, and the LEGB rule.
Scope
Scope is the idea that a variable does not exist everywhere in your program — it only exists in certain places. Where you create a variable determines where you can use it.
Think of it like rooms in a house. A notebook in your bedroom is only accessible in your bedroom. To use it in the kitchen, you would have to carry it out. Python works the same way — variables created inside a function stay inside that function unless you deliberately bring them out.
Local scope
A variable created inside a function is local to that function. It only exists while the function is running and disappears when it finishes.
def greet():
message = "Hello!" # local variable
print(message)
greet() # Hello!
print(message) # NameError — message does not exist out heremessage was born inside greet() and dies when greet() finishes. The outside world cannot see it.
Two functions can have local variables with the same name — they do not interfere with each other:
def func_a():
x = 10
print(x) # 10
def func_b():
x = 99
print(x) # 99
func_a()
func_b()Each x lives in its own function. They are completely separate.
Global scope
A variable created at the top level of your file — outside any function — is global. It is accessible everywhere.
name = "Ali" # global variable
def greet():
print(f"Hello, {name}!") # can read the global variable
greet() # Hello, Ali!
print(name) # AliReading a global variable from inside a function is fine. But modifying it is where things get tricky:
count = 0
def increment():
count += 1 # UnboundLocalError
increment()This fails. When Python sees count += 1 inside the function, it assumes count is a local variable — but it was never assigned locally, so it does not exist yet.
To modify a global variable inside a function, you must declare it with the global keyword:
count = 0
def increment():
global count
count += 1
increment()
increment()
print(count) # 2global count tells Python — do not create a new local variable, use the one that already exists in the global scope.
Using global is generally a sign that your code could be better structured. It creates hidden dependencies between functions and the global state, which makes bugs harder to track. Prefer passing values as arguments and returning results instead.
Enclosing scope
When you define a function inside another function, the inner function can access variables from the outer function. This is called enclosing scope.
def outer():
message = "Hello from outer"
def inner():
print(message) # can access outer's variable
inner()
outer() # Hello from outermessage is not global — it belongs to outer(). But inner() can still read it because it is enclosed inside outer().
nonlocal — modifying an enclosing variable
Just like global, if you want to modify a variable from the enclosing function, you need to declare it with nonlocal:
def outer():
count = 0
def inner():
nonlocal count
count += 1
print(f"count is now {count}")
inner()
inner()
inner()
outer()Output:
count is now 1
count is now 2
count is now 3Without nonlocal, count += 1 would try to create a new local variable inside inner() and fail with an UnboundLocalError.
The LEGB rule
When you use a variable name, Python searches for it in a specific order — Local, Enclosing, Global, Built-in. This is called the LEGB rule.
Python starts at the innermost scope and works outward. The first place it finds the name, it uses it. If it reaches the end without finding it, you get a NameError.
L — Local
The current function's own variables:
def greet():
name = "Ali" # local
print(name) # found in L — used immediatelyE — Enclosing
The outer function's variables when functions are nested:
def outer():
name = "Ali" # enclosing
def inner():
print(name) # not in L, found in E
inner()G — Global
The top level of the file:
name = "Ali" # global
def greet():
print(name) # not in L, not in E, found in GB — Built-in
Python's own built-in names like print, len, range, type:
def greet():
print(len("Ali")) # print and len found in BYou never define print or len yourself — Python provides them in the built-in scope.
Shadowing — when local hides global
If you create a local variable with the same name as a global one, the local one shadows the global inside that function:
name = "Global Ali"
def greet():
name = "Local Ali" # shadows the global
print(name) # Local Ali
greet()
print(name) # Global Ali — global is untouchedInside greet(), Python finds name in local scope (L) and never looks further. The global name is hidden but not changed.
A real example
A counter that uses enclosing scope to keep state:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3count lives in the enclosing scope of make_counter(). Every time you call counter(), it increments that same variable. This pattern is called a closure — the inner function remembers the environment it was created in. You will see this used heavily in decorators.
Summary
| Scope | Where | Keyword to modify |
|---|---|---|
| Local | Inside the current function | — |
| Enclosing | Outer function when nested | nonlocal |
| Global | Top level of the file | global |
| Built-in | Python's own names | — |
- Python searches L → E → G → B in that order
- Reading a global variable is fine — modifying it needs
global - Modifying an enclosing variable needs
nonlocal - Avoid
globalwhen you can — pass values as arguments instead