Python Variable Scope Explained Clearly

Updated on: May 28, 2026
Reading time: 9 minutes
Logo do Python com as palavras global e local

Python variable scope explains where a variable can be accessed, where it can be changed, and why the same name may behave differently in different parts of a program. Many beginner bugs happen because a variable works inside a function but fails outside it, or because changing a value inside a function does not update the value you expected. Once you understand scope, functions become easier to write, debug, and reuse.

This English version is adapted for readers who want a practical explanation, not a literal translation. You will learn local scope, global scope, enclosing scope, built-in scope, the LEGB lookup rule, the difference between reading and assigning variables, how global works, how nonlocal works, and which mistakes to avoid. If you are still learning the basics, start with this Python beginner guide and this article about variables in Python.

What Is Variable Scope in Python?

Scope is the region of a program where a name is visible. A variable created inside a function usually belongs to that function. A variable created at the top level of a file usually belongs to the module. Python uses scope rules to decide which value a name refers to when the same name appears in more than one place.

Scope keeps programs predictable. Without scope, every variable name would risk colliding with every other variable name. Functions could accidentally overwrite values from unrelated parts of the code. By keeping names separated, Python lets you build independent functions and modules. The official Python scopes and namespaces documentation explains these rules in the language tutorial.

The Four Main Scopes: LEGB

Python resolves names using the LEGB rule: Local, Enclosing, Global, Built-in. When Python sees a variable name, it searches in that order. First it checks the local scope. If the name is not found, it checks enclosing function scopes. Then it checks the global module scope. Finally, it checks built-in names such as len, print, and range.

  • Local: names created inside the current function.
  • Enclosing: names from an outer function when functions are nested.
  • Global: names created at the top level of the current module.
  • Built-in: names provided by Python automatically.

This lookup order is one of the most important rules for understanding why a variable is found or not found. It also explains why naming a variable list, dict, or str can cause confusion: you may shadow a built-in name that Python normally provides.

Local Scope

A local variable is created inside a function. It exists only while that function is running and cannot be accessed directly from outside the function. This keeps temporary values private to the function and prevents accidental conflicts with other parts of the program.

def calculate_total(price, quantity):
    total = price * quantity
    return total

print(calculate_total(10, 3))
print(total)  # NameError

In this example, total is local to calculate_total(). The function can use it, but code outside the function cannot. If you need the value outside the function, return it. Returning values is clearer than trying to access local variables from the outside. For a deeper explanation, read this guide to functions in Python.

Global Scope

A global variable is defined at the top level of a module, outside any function or class. It can be read from inside functions, but assigning to it from inside a function requires special care. Global variables can be useful for constants and configuration, but they should not become a place where every function shares mutable state.

APP_NAME = "Inventory System"

def show_app_name():
    print(APP_NAME)

show_app_name()

This works because the function only reads the global variable. Constants written in uppercase are a common pattern. They communicate that the value should not change during normal execution. Problems usually start when functions modify global variables as part of business logic.

Reading vs Assigning a Global Variable

Python treats assignment as a signal that a name is local unless told otherwise. This means a function can read a global variable, but if it assigns to the same name, Python assumes that name is local inside the function. That can lead to UnboundLocalError when you try to read the value before assignment.

counter = 0

def increment():
    counter = counter + 1  # UnboundLocalError
    return counter

This error happens because Python sees counter = inside the function and decides that counter is local. Then the right side tries to read the local variable before it has a value. This rule surprises many beginners, but it is consistent once you understand that assignment creates a local binding by default.

The global Keyword

The global keyword tells Python that a name inside a function refers to the module-level variable, not a new local variable. It can solve the previous error, but it should be used carefully. Code that changes global state can become harder to test and debug.

counter = 0

def increment():
    global counter
    counter = counter + 1
    return counter

print(increment())
print(increment())

This works, but it is not always the best design. In many cases, returning a new value is cleaner than mutating a global value. Use global when you truly mean to change module-level state, not as a quick fix for every scope error.

A Cleaner Alternative: Return Values

Instead of changing a global variable, pass values into a function and return the result. This makes the function easier to understand because its inputs and outputs are explicit. It also makes the function easier to test because it does not depend on hidden external state.

def increment(counter):
    return counter + 1

counter = 0
counter = increment(counter)
counter = increment(counter)
print(counter)

This version is more predictable. The function receives a value and returns a value. That style is especially useful in larger programs where hidden global mutations can cause bugs that are difficult to trace. If you want cleaner function contracts, also review Python type hints.

Enclosing Scope and Nested Functions

Enclosing scope appears when a function is defined inside another function. The inner function can read variables from the outer function. This is part of the LEGB rule: after local scope, Python checks enclosing scopes before checking global scope.

def outer():
    message = "Hello from outer"

    def inner():
        print(message)

    inner()

outer()

The inner function can read message because it exists in the enclosing function. This pattern is common in decorators, callbacks, closures, and helper functions. If you are learning decorators later, this guide to Python decorators connects directly with enclosing scope.

The nonlocal Keyword

The nonlocal keyword lets an inner function assign to a variable from an enclosing function. It does not refer to global variables. It specifically targets the nearest enclosing function scope where the name exists. This is useful for closures that need to preserve and update state.

def make_counter():
    count = 0

    def increment():
        nonlocal count
        count = count + 1
        return count

    return increment

counter = make_counter()
print(counter())
print(counter())

Here, count belongs to make_counter(), not to the global module. The inner increment() function uses nonlocal so it can update that enclosing variable. Without nonlocal, Python would treat count inside increment() as a new local variable.

Built-in Scope

Built-in scope contains names that Python provides automatically, such as print, len, sum, range, dict, and list. These names are available without imports. Python checks built-in scope last, after local, enclosing, and global scope.

numbers = [1, 2, 3]
print(len(numbers))

Avoid naming your variables after built-ins. For example, do not write list = [1, 2, 3]. That shadows the built-in list type and can break later code that tries to call list(). This kind of name collision is subtle and frustrating.

Scope Is Not the Same as Mutability

A common confusion is the difference between rebinding a name and mutating an object. If a global variable points to a list, a function can mutate the list without using global. But if the function assigns a new list to the same name, then Python treats the name as local unless declared global.

items = []

def add_item(value):
    items.append(value)  # mutates the list object

add_item("Python")
print(items)

This works because items.append() changes the existing list object. It does not create a new local binding named items. However, relying on mutable global objects can still make code harder to reason about. If you use collections often, review Python lists and Python dictionaries.

Scope in Loops and If Blocks

Python does not create a new scope for normal if, for, or while blocks. A variable assigned inside these blocks remains available in the surrounding function or module scope. This is different from some other languages where block scope is stricter.

if True:
    language = "Python"

print(language)

This works because the if block does not create a separate local scope. Functions, classes, comprehensions, and modules are more important scope boundaries in Python. If loops are still new, this guide to loops in Python can help.

Common Scope Mistakes

The first mistake is trying to access a local variable outside its function. The second is assigning to a global name inside a function and expecting Python to update the global variable automatically. The third is shadowing built-ins such as list, dict, or str. The fourth is using too many global variables instead of passing values explicitly.

Another common mistake is reusing the same variable name at different levels of the program. This is legal, but it can confuse readers. Prefer meaningful names that describe the role of the value in that specific scope. Clear naming often prevents scope bugs before they happen.

How to Debug Scope Problems

When a variable is not found, read the traceback carefully. NameError usually means Python could not find the name in any visible scope. UnboundLocalError usually means Python decided the name is local because of assignment, but you tried to read it before assigning a value. These two errors point to different fixes.

Use small print statements, a debugger, or focused tests to check where a variable is created and where it is used. Avoid fixing scope errors by making everything global. That may remove the immediate error but usually makes the program harder to maintain. For step-by-step debugging, read this guide to debugging Python with pdb.

Best Practices for Variable Scope

Keep variables in the smallest useful scope. Prefer function parameters and return values over mutable globals. Use uppercase names for constants. Avoid shadowing built-in names. Use global rarely and deliberately. Use nonlocal only when a closure truly needs to update enclosing state. Give variables clear names so readers can understand where they belong.

Good scope management leads to cleaner functions. A function that receives inputs, works with local variables, and returns a result is easier to test than a function that reads and changes hidden global state. This is one of the foundations of maintainable Python code.

Final Checklist

Remember the LEGB rule: Local, Enclosing, Global, Built-in. Local variables belong to functions. Global variables belong to modules. Nested functions can read enclosing variables. Use nonlocal to update enclosing variables. Use global to update module-level variables, but avoid it when a return value would be cleaner. Do not confuse scope with mutability.

Variable scope is not just theory. It affects how your functions communicate, how bugs appear, and how easy your code is to maintain. Once you understand scope, Python starts to feel more predictable, and errors involving missing or unexpected variables become much easier to diagnose.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Comparativo dos melhores cursos de Python para aprender programação
    Fundamentals
    Foto de perfil de Leandro Hirt da Academify

    8 Best Python Courses for Beginners in 2026

    Compare the 8 best Python courses for beginners in 2026, including Academify, Alura, Udemy, Coursera, Codecademy, Harvard CS50, EBAC, and

    Ler mais

    Tempo de leitura: 7 minutos
    28/05/2026
    Como usar f-strings para formatar números e moedas em Python
    Fundamentals
    Foto de perfil de Leandro Hirt da Academify

    Python f-strings: Format Numbers and Currency

    Learn how to use Python f-strings to format numbers, currency, percentages, alignment, and zero-padding with clean and practical examples.

    Ler mais

    Tempo de leitura: 6 minutos
    28/05/2026
    Uso do operador ternário em Python para condições rápidas
    Fundamentals
    Foto de perfil de Leandro Hirt da Academify

    Python Ternary Operator: Clean One-Line If

    Learn how Python ternary operators work, when to use one-line if expressions, how to avoid nested logic, and how to

    Ler mais

    Tempo de leitura: 8 minutos
    21/05/2026
    Logo lambda usado no Half-Life
    Fundamentals
    Foto de perfil de Leandro Hirt da Academify

    Python Lambda Functions Explained Clearly

    Learn Python lambda functions with clear examples, syntax, sorting, map, filter, closures, common mistakes, and when to prefer def.

    Ler mais

    Tempo de leitura: 8 minutos
    21/05/2026
    Como usar o comando return em funções Python
    Fundamentals
    Foto de perfil de Leandro Hirt da Academify

    Python return Statement Explained Clearly

    Learn how Python return works, how it differs from print, how to return multiple values, avoid None bugs, and write

    Ler mais

    Tempo de leitura: 9 minutos
    21/05/2026
    Explicação prática de args e kwargs em funções Python
    Fundamentals
    Foto de perfil de Leandro Hirt da Academify

    Python *args and **kwargs Explained Clearly

    Learn how Python *args and **kwargs work, when to use them, how unpacking works, and how to avoid common mistakes

    Ler mais

    Tempo de leitura: 8 minutos
    19/05/2026