FunctionsIntermediate8 min37 / 63

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

Visual walkthrough1 / 6
1

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.

Scope is why a variable can seem to 'disappear' inside a function.

#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.

message only exists inside make_greeting()
def make_greeting():
    message = "Hello, world!"  # local variable
    print(message)

make_greeting()
print(message)  # NameError — message doesn't exist out here
Think of it like

Think 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.

Functions can read global variables without any special keyword
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.

LEGB in action: inner() finds x in the enclosing scope first
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.

Python decides score is local but it has no value yet — crash!
score = 0

def add_point():
    score = score + 1  # Python sees assignment, makes it local
    print(score)

add_point()
Common mistake

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."

global score tells Python to use the top-level variable
score = 0

def add_point():
    global score
    score = score + 1

add_point()
add_point()
print(score)
Watch out

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.

nonlocal lets increment() modify count in make_counter()
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.

No globals needed — data flows in and out cleanly
def add_point(current_score):
    return current_score + 1

score = 0
score = add_point(score)
score = add_point(score)
print(score)
Tip

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.

Quick check

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.
Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

What does this code print?

predict-output
x = "global"

def outer():
    x = "enclosing"

    def inner():
        print(x)

    inner()

outer()
Fix the bug#2

This code is supposed to add 1 to score each time add_point() is called, but it crashes. What is wrong?

fix-bug
score = 0

def add_point():
    score = score + 1

add_point()
print(score)
Fill in the blank#3

Complete the function so it correctly modifies the global variable total.

total = 10

def add_five():
     total
    total = total + 5

add_five()
print(total)
Reorder the lines#4

Put these lines in the right order to define a counter using nonlocal, then print 1 and 2.

1
    increment()
2
    count = 0
3
        print(count)
4
    def increment():
5
make_counter()
6
        count = count + 1
7
        nonlocal count
8
    increment()
9
def make_counter():
Your turn
Practice exercise

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:

solution.py · editable