Variable scope in Python

Suppose a program has two functions, and both use a variable named result inside:

Python 3.13
def area_circle(radius):
    result = 3.14 * radius * radius
    return result

def area_square(side):
    result = side * side
    return result

These two result variables don't interfere with each other: each function works with its own name, and one doesn't affect the other. This is called variable scope: the rule Python uses to decide which variable a name refers to at any given point in the code.

What is variable scope?

Scope is the part of the program where a variable is available. It determines where a name can be used and which object it refers to.

Python has two main scope levels:

  • global scope: anything declared at the top level of a module, visible inside every function
  • local scope: variables declared inside a function, alive only while that function is running

A useful image: scopes are like rooms in an apartment. Things on your personal desk are accessible only to you (a local variable inside a function). A note on the fridge is visible to the whole family (a global variable).

Global and local variables: main program and two functions

Global variables

Global variables are defined outside any function and accessible both from within functions and outside them:

Python 3.13
# Create a global variable
message = "Hello, world!"

def show_message():
    # Use the global variable inside the function
    print(message)

show_message()
Hello, world!
print(f"Variable outside the function: {message}")
Variable outside the function: Hello, world!

The more globals you use, the harder the code is to debug: to understand what a function does, you have to look beyond its arguments and also remember which outside variables it reads.

Local variables

Local variables are created inside a function and exist only while that function is running. Once it returns, they disappear from memory:

Python 3.13
def calculate_sum():
    a = 10
    b = 20
    result = a + b
    print(f"Sum inside the function: {result}")

calculate_sum()
Sum inside the function: 30
# Trying to access a local variable outside the function
try:
    print(f"result: {result}")
except NameError as e:
    print(f"Error: {e}")
Error: name 'result' is not defined

The variable result exists only inside calculate_sum() and doesn't leak out.

When names collide

If a function creates a variable with the same name as a global one, the local one takes precedence. This is called shadowing: the local "shadows" the global without touching it:

Python 3.13
x = "global"

def test_scope():
    x = "local"
    print(f"Inside function: x = {x}")

test_scope()
Inside function: x = local
print(f"Outside function: x = {x}")
Outside function: x = global

These are two completely different variables that happen to share a name. Like two people with the same name living in different cities: same name, different individuals.

Modifying a global variable from a function

By default, any assignment inside a function is treated as creating a new local variable:

Python 3.13
x = "global"

def modify_global():
    x = "new local"  # This is a local, the global is unchanged
    print(f"Inside function: x = {x}")

modify_global()
Inside function: x = new local
print(f"Outside function: x = {x}")
Outside function: x = global

To actually have a function change a global variable, you must explicitly say global x at the start of the function:

Python 3.13
x = "global"

def modify_global():
    global x
    x = "global changed"
    print(f"Inside function: x = {x}")

modify_global()
Inside function: x = global changed
print(f"Outside function: x = {x}")
Outside function: x = global changed

The global keyword signals to anyone reading the code: "this function isn't just doing its own job, it reaches outside." For that reason, use it only when no other path is available.

The most common trap: UnboundLocalError

A function reads a global variable and immediately reassigns it:

Python 3.13
x = 10

def show():
    print(x)        # read
    x = x + 1       # and reassign right after

You'd expect the function to print 10 and bump x up to 11. Instead, running it crashes with UnboundLocalError: local variable 'x' referenced before assignment.

What's going on? Python looks at the function as a whole before executing it and decides which names are local. It sees x = ... inside, so x is declared in this function and is therefore local. On the top line, print(x) then tries to read the local x, which doesn't yet have a value, and crashes.

For this function to work with the global x, you'd need global x at the top. But usually there's a cleaner path: don't touch the global, take x as a parameter and return the new value:

Python 3.13
def increment(value):
    return value + 1

x = 10
x = increment(x)
print(x)
11

When global is justified, and when it isn't

The most predictable way to handle data is: don't modify globals; pass values through parameters and return new ones via return:

Python 3.13
def add(a, b):
    return a + b

x = 5
y = 10
sum_result = add(x, y)
print(f"{x} + {y} = {sum_result}")
5 + 10 = 15

This kind of function is honest: its result is fully determined by what came in. Nothing happens behind the scenes. That makes both testing and reading easier: a glance at the signature tells you everything.

global is justified when a variable really should live at program level: a configuration value, a shared counter. But in most practice tasks it isn't needed.

Understanding check

Let's see how well you've understood variable scope.


In the next lesson we'll look at basic data types in Python: what's behind int, str, float, bool, and which operations are defined on each.