Variable Scope
Learn where variables live, which parts of your code can see them, and why keeping variables close to where they are used makes your programs easier to understand.
Imagine your home has different rooms. A notebook kept in your bedroom is only accessible in your bedroom — you cannot reach it from the kitchen without walking to get it. Variables in Python work the same way. Every variable has a scope: the region of code where it exists and can be used.
Understanding scope is one of those skills that quietly saves you hours of frustration. It explains why a variable seems to "disappear" inside a function, or why changing a variable in one place does not affect another.
See it in action
— step through the idea, then dive into the details below.Where Can a Variable Be Seen?
Every variable has a scope — the region of code where it exists and can be used. Think of variables like notebooks in different rooms: a notebook in the bedroom can't be read from the kitchen.
#Local Scope: Variables Born Inside a Function
When you create a variable inside a function, it is local to that function. It springs into life when the function runs and disappears when the function finishes. Code outside the function cannot see it at all.
def make_greeting():
message = "Hello, world!" # local variable
print(message)
make_greeting()
print(message) # NameError — message doesn't exist out hereThink of a function like a pop-up tent
Everything created inside the tent stays inside the tent. Once you fold it up (the function ends), everything inside is gone. People standing outside the tent never knew those things existed.
#Global Scope: Variables at the Top Level
A variable created outside any function lives in global scope. Every function in the same file can read it — but reading and changing are two different things, as you will soon see.
country = "Canada" # global variable
def show_country():
print(country) # reading a global is fine
show_country()#The LEGB Rule: How Python Finds a Variable
When Python sees a variable name, it searches for it in this order:
- L — Local: inside the current function
- E — Enclosing: inside any outer functions that wrap this one
- G — Global: at the top level of the file
- B — Built-in: names Python always provides, like
print,len,range
Python stops as soon as it finds the name. If it reaches the end of the list without finding it, you get a NameError.
x = "global"
def outer():
x = "enclosing"
def inner():
print(x) # finds 'enclosing' before 'global'
inner()
outer()#Trying to Change a Global Inside a Function
Here is the tricky part. If you try to assign to a global variable inside a function, Python does not change the global — it creates a brand new local variable with the same name instead.
score = 0
def add_point():
score = score + 1 # Python sees assignment, makes it local
print(score)
add_point()Assignment makes a variable local
The moment Python sees score = ... inside a function, it treats every use of score in that function as local — even the ones that appear before the assignment line. This is why the error says "referenced before assignment" even though score exists globally.
#The global Keyword
If you genuinely need a function to modify a global variable, you must declare your intention with the global keyword. This tells Python: "When I say score, I mean the one at the top level."
score = 0
def add_point():
global score
score = score + 1
add_point()
add_point()
print(score)Use global sparingly
Relying heavily on global variables makes code hard to reason about. When any function anywhere can change a global, tracking down bugs becomes a nightmare. Prefer returning values from functions and passing data in as arguments instead.
#The nonlocal Keyword (Nested Functions)
When you have a function inside another function, nonlocal lets the inner function modify a variable that belongs to the enclosing (outer) function — not the global level.
def make_counter():
count = 0
def increment():
nonlocal count
count = count + 1
print(count)
increment()
increment()
increment()
make_counter()#The Better Pattern: Return Values
Instead of reaching out to modify a global, the cleanest approach is to pass data in as a parameter and return the new value out. This keeps each function self-contained and easy to test.
def add_point(current_score):
return current_score + 1
score = 0
score = add_point(score)
score = add_point(score)
print(score)Keep variables close to where they are used
A local variable is like a sticky note on your desk — it is right there when you need it and gone when you are done. A global variable is like a whiteboard in the hallway — everyone can see and change it, which leads to confusion. Default to local; reach for global only when you truly need to.
What will the following code print? ```python value = 10 def double(): value = value * 2 print(value) double() ```
Key takeaways
- A variable created inside a function is **local** — invisible to the outside world and gone when the function ends.
- Python looks up names using the **LEGB rule**: Local, Enclosing, Global, Built-in — in that order.
- Assigning to a name inside a function makes Python treat it as **local everywhere** in that function, even before the assignment line.
- Use `global` to modify a top-level variable and `nonlocal` to modify a variable in an enclosing function — but prefer passing arguments and returning values instead.
- Keeping data local and passing it explicitly makes code easier to read, test, and debug.
What does this code print?
x = "global"
def outer():
x = "enclosing"
def inner():
print(x)
inner()
outer()This code is supposed to add 1 to score each time add_point() is called, but it crashes. What is wrong?
score = 0
def add_point():
score = score + 1
add_point()
print(score)Complete the function so it correctly modifies the global variable total.
total = 10 def add_five(): total total = total + 5 add_five() print(total)
Put these lines in the right order to define a counter using nonlocal, then print 1 and 2.
increment()
count = 0
print(count)
def increment():
make_counter()
count = count + 1
nonlocal count
increment()
def make_counter():
Write a function called apply_discount that takes two parameters: price (a number) and discount_percent (a number like 10 for 10%). It should calculate and return the discounted price. Then call it and print the result. Do NOT use any global variables — keep all data local or passed as arguments.
Try it live — edit the code and hit Run to execute real Python: