PYTHON • FUNCTIONS AND PROGRAM STRUCTURE

Scope: Local, Global & nonlocal — Understand local scope, global scope, and nonlocal (conceptual)

Learn how Python decides which variable you mean when the same name appears in different parts of your code.

Historical Context & Motivation

Imagine you and your friend both have a dog named Max. At your house, "Max" means your golden retriever. At your friend's house, "Max" means their terrier. Neither household gets confused because each home is a separate scope — a boundary that determines which Max you're talking about. Programming languages face the exact same problem: when two variables share a name, the computer needs rules to decide which one you mean.

Early programming languages in the 1950s and 1960s had very simple approaches to this problem. In some early versions of BASIC, every variable was global — visible everywhere in the program. This meant that if you used a variable called x in one part of your code, it could accidentally be changed by a completely different part. As programs grew larger, this became a nightmare for debugging.

1958
ALGOL Introduces Block Scope
The ALGOL language pioneered the idea that variables could be confined to specific blocks of code, establishing the concept of lexical scoping that nearly all modern languages use today.
1970s
C Popularizes Function-Level Scope
The C programming language made function-level local variables mainstream. Variables declared inside a function existed only while that function ran, preventing unintended side effects.
1991
Python 0.9 Released
Guido van Rossum released the first version of Python with support for local and global scope, drawing on lessons learned from decades of language design.
2001
Python 2.1 Adds Nested Scope
PEP 227 introduced nested (enclosing) scope and read access to variables in outer functions, paving the way for closures in Python.
2006
PEP 3104 — The nonlocal Keyword
Python 3 introduced the nonlocal keyword, finally letting inner functions modify variables in an enclosing function's scope without resorting to workarounds.

The central question scope answers is deceptively simple: when your code mentions a variable name, where does Python look for it, and what happens if the same name exists in multiple places? Understanding this question is essential to writing functions that behave predictably and don't produce mysterious bugs.

Core Principles & Definitions

In Python, scope refers to the region of your program where a particular variable name is accessible. Think of scope as a set of boundaries — like walls in a building — that determine which parts of your code can "see" a variable. Python organizes these boundaries into a clear hierarchy, and understanding the hierarchy is the key to mastering scope.

1

Local Scope

A variable created inside a function belongs to that function's local scope. It is born when the function starts and destroyed when the function ends. No code outside the function can access it.
2

Global Scope

A variable defined at the top level of your script (outside any function) lives in the global scope. It can be read from inside functions, but modifying it requires the global keyword.
3

Enclosing (Nonlocal) Scope

When a function is defined inside another function, the inner function can access variables from the outer function's scope. This middle layer is called the enclosing scope, and the nonlocal keyword lets you modify those variables.
4

Built-in Scope

Python's pre-loaded names like print, len, and range live in the built-in scope. This is the outermost layer — Python checks here last.
✦ KEY TAKEAWAY
Think of Python's scope system like looking for a book in a school. First, you check your desk (local scope). If it's not there, you check your classroom bookshelf (enclosing scope). Still not found? You try the school library (global scope). As a last resort, you visit the public library (built-in scope). Python searches from the innermost scope outward — this is called the LEGB rule: Local → Enclosing → Global → Built-in.

Visual Explanation — The LEGB Rule

The nested boxes show the four scope levels in Python's LEGB rule. When Python encounters a variable name, it starts searching from the innermost box (Local) and works outward through Enclosing, Global, and finally Built-in.

In the diagram above, notice that each scope is physically contained within the next larger scope. The local scope (innermost cyan box) is where Python always begins its search. If the variable isn't found there, Python moves outward to the enclosing scope (pink box), then to the global scope (violet box), and finally to the built-in scope (amber box). If the name isn't found in any of these four layers, Python raises a NameError.

How Scope Works in Practice

Reading vs. Writing — The Critical Difference

One of the trickiest parts of scope is the difference between reading a variable and writing to a variable. Python lets you read a global variable from inside a function without any special syntax. However, if you try to assign a value to that name inside the function, Python assumes you're creating a new local variable with the same name — it does not modify the global one.

The global Keyword

To explicitly tell Python "I want to modify the global variable, not create a local one," you use the global keyword at the top of your function. This declaration forces Python to skip creating a local variable and instead work directly with the global one. Here's the pattern:

💡 Pattern: Using global
score = 0 def add_point(): global score ← tells Python to use the global score score = score + 1 Without the global declaration, the assignment score = score + 1 would cause an UnboundLocalError because Python sees the assignment and treats score as local, but then tries to read it before it has a value.

The nonlocal Keyword

The nonlocal keyword solves the same problem as global, but for nested functions. When you have a function defined inside another function, nonlocal tells the inner function to modify the variable in its enclosing function's scope rather than creating a new local variable. Without nonlocal, the inner function can read the enclosing variable but cannot reassign it.

💡 Pattern: Using nonlocal
def outer(): count = 0 def inner(): nonlocal count ← modifies outer's count count = count + 1 inner() print(count) # prints 1
⚡ REMEMBER
Python's default behavior is protective: assignments inside functions always create local variables unless you explicitly say otherwise with global or nonlocal. This prevents accidental changes to variables in other scopes — like having a lock on each room's door so visitors can look through the window (read) but can't rearrange the furniture (write) without a key.

LEGB Lookup — Step by Step

Let's trace exactly how Python resolves a variable name through the LEGB chain with a concrete code example. Understanding this flow is the single most important skill for debugging scope-related errors.

The flowchart shows how Python resolves the name x when print(x) is called inside inner(). It checks each scope in LEGB order, stopping at the first match. The left panel shows how removing definitions changes which value is found.

The flowchart illustrates three important scenarios. In Case A, all three x definitions exist, so Python finds x immediately in the local scope and prints "local". In Case B, we remove the local assignment, so Python skips L and finds x in the enclosing scope. In Case C, both local and enclosing are removed, so Python reaches the global scope. The key insight is that a local variable with the same name as a global variable "shadows" the global one — the global variable still exists, but it's hidden from view inside that function.

⚠️ Variable Shadowing
Shadowing occurs when a variable in an inner scope has the same name as one in an outer scope. The inner variable doesn't delete or change the outer one — it just blocks your view of it from within that scope. When the inner scope ends, the outer variable is visible again. This is a common source of bugs when you accidentally reuse a variable name!

Worked Example — Tracing Scope

Let's carefully trace through a program that uses local, global, and nonlocal scope to understand exactly what happens at each step.

📝 The Code
message = "hello" def greet(): message = "hi" def excited(): nonlocal message message = message + "!!!" excited() print("Inside greet:", message) greet() print("Global:", message)
Tracing the Execution
1
Step 1 — Global assignmentPython executes message = "hello" at the top level. This creates a variable message in the global scope with the value "hello".
Global scope: message = "hello"
2
Step 2 — Call greet()Python calls greet() and enters the function. A new local scope is created for greet. The line message = "hi" creates a new local variable — it does NOT change the global message.
greet's local scope: message = "hi" | Global: message = "hello" (unchanged)
3
Step 3 — Call excited()Python calls excited(). The nonlocal message declaration tells Python: "When I say message in this function, I mean the one from the enclosing scope (greet's local variable)." So message = message + "!!!" reads "hi" from greet's scope and changes it to "hi!!!".
greet's local scope: message = "hi!!!" (modified by nonlocal)
4
Step 4 — Print inside greet()After excited() returns, print("Inside greet:", message) executes inside greet. It finds message in the local scope with the updated value.
Output: Inside greet: hi!!!
5
Step 5 — Print in global scopeAfter greet() returns, Python executes print("Global:", message) at the top level. The global message was never touched — neither greet nor excited used the global keyword.
Output: Global: hello
✦ KEY INSIGHT
Notice how the nonlocal keyword in excited() modified greet's local variable but left the global variable completely untouched. Each scope is like a separate notebook — nonlocal lets you write in your teacher's notebook, but it doesn't touch the principal's notebook (global).

global vs. nonlocal — When to Use Each

Students often confuse global and nonlocal because they both solve the same basic problem: letting a function modify a variable from an outer scope. However, they target different levels of the scope hierarchy and have different use cases.

Comparison of global and nonlocal keywords
Featureglobalnonlocal
Target scopeThe module-level (top-level) scopeThe nearest enclosing function's scope
Requires nesting?No — works in any functionYes — only in a function inside another function
Variable must exist?No — can create a new global variableYes — the variable must already exist in an enclosing scope
Common use caseConfiguration values, counters shared across many functionsClosures, counters in nested helper functions
Best practiceUse sparingly — overuse makes code hard to debugUseful for closures; generally cleaner than global
🎯 BEST PRACTICE
In professional Python code, both global and nonlocal are used cautiously. The cleanest approach is often to pass values as arguments and return results rather than modifying outer variables directly. Think of it like a well-run restaurant kitchen: instead of everyone reaching into the same pot (shared mutable state), each chef receives ingredients, prepares their dish, and passes the finished plate to the next station.

Connection to Advanced Concepts

Understanding scope is a gateway to more powerful programming concepts. Once you're comfortable with how Python resolves names, you'll be ready to explore closures, decorators, and even object-oriented programming — all of which rely heavily on scope rules.

How scope concepts connect to advanced Python topics
What You Know NowWhere It Leads
Local variables are created when a function runs and destroyed when it endsClosures — an inner function can "remember" variables from its enclosing scope even after the outer function has finished
Functions can be nested inside other functionsDecorators — functions that wrap other functions to add extra behavior, using nested scope extensively
The LEGB rule determines name resolutionNamespaces & modules — each imported module has its own global scope, preventing name collisions across files
Scope isolates variables to prevent accidental modificationEncapsulation in classes — object-oriented programming uses similar ideas to protect data inside objects

As you continue learning Python, you'll find that nearly every design pattern and advanced technique builds on the foundation of scope. A closure, for instance, is essentially a function that takes advantage of the LEGB rule by "capturing" variables from its enclosing scope. Mastering scope now will make these future topics feel like natural extensions rather than brand-new concepts.

Practice Problems

PROBLEM 1 — CONCEPTUAL
What does the acronym LEGB stand for, and in what order does Python search these scopes when it encounters a variable name? Why does the order matter?
PROBLEM 2 — BASIC
Predict the output of this code: x = 5 def change_x(): x = 10 print(x) change_x() print(x)
PROBLEM 3 — INTERMEDIATE
The following code produces an error. Identify the error, explain why it occurs, and fix the code so that it correctly updates the global total variable. total = 0 def add_to_total(n): total = total + n add_to_total(5) print(total)
PROBLEM 4 — APPLIED
You're building a simple counter function. Predict the output of this code, then explain how nonlocal makes it work. def make_counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment my_counter = make_counter() print(my_counter()) print(my_counter()) print(my_counter())
PROBLEM 5 — CRITICAL THINKING
A classmate writes the following code and asks you why it doesn't work as expected. They want inner() to modify the global variable score, but the output is 0. Explain the bug and propose two different fixes. score = 0 def outer(): score = 0 def inner(): nonlocal score score += 10 inner() outer() print(score)

Summary — Scope: Local, Global & nonlocal

Python's scope system determines where a variable can be accessed in your code. Variables exist in one of four layers, searched in LEGB order: Local (inside the current function), Enclosing (an outer function's scope), Global (the top level of your script), and Built-in (Python's pre-loaded names like print and len). Assignments inside a function create local variables by default.

To modify a global variable from inside a function, use the global keyword. To modify a variable in an enclosing function's scope, use the nonlocal keyword. Without these keywords, Python protects outer variables from accidental changes — a design choice called variable shadowing. When a local variable shares a name with a global one, the local version hides (but does not delete) the global. Understanding scope is essential for writing clean, bug-free functions and is the foundation for advanced topics like closures and decorators.

Varsity Tutors • Python • Scope: Local, Global & nonlocal