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.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 4 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

Visual Explanation — The LEGB Rule

B — BUILT-IN SCOPEprint(), len(), range(), int(), str() ...G — GLOBAL SCOPEx = 10 name = "Alice"E — ENCLOSING SCOPEdef outer(): y = 20L — LOCAL SCOPEdef inner(): z = 30Python searches HERE firstThen outward → E → G → BSEARCH DIRECTION →
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.

SECTION 4

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.

SECTION 5

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.

LEGB Name Resolution FlowchartCODEx = "global"def outer():x = "enclosing"def inner():x = "local"print(x)Case A: all three x's exist→ prints: "local"Case B: remove local x→ prints: "enclosing"Case C: remove local & enclosing x→ prints: "global"L — Local?x = "local"E — Enclosing?x = "enclosing"G — Global?x = "global"B — Built-in?print, len, int ...NameError!Not foundNot foundNot foundNot foundFound! Use itFound! Use itFound! Use itFound! Use it
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!

SECTION 6

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

    Step 1 — Global assignment

    Python 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. 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. 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. 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. 5

    Step 5 — Print in global scope

    After 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).

SECTION 7

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.

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

Comparison of global and nonlocal keywords

🎯

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.

SECTION 8

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.

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

How scope concepts connect to advanced Python topics

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.

SECTION 9

Practice Problems

Question 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?
Show answer
LEGB stands for Local, Enclosing, Global, Built-in. Python searches in that exact order, starting with the innermost scope. The order matters because Python stops at the first match it finds. This means a local variable with the same name as a global variable will "shadow" the global one — the function will use the local version and never even check the global scope. If no match is found in any scope, Python raises a NameError.

Question 2: BASIC

Predict the output of this code: x = 5 def change_x(): x = 10 print(x) change_x() print(x)
Show answer
The output is 10 followed by 5. Inside change_x(), the assignment x = 10 creates a new local variable that shadows the global x. The print(x) inside the function finds the local x (value 10). After the function ends, the local variable is destroyed. The second print(x) in the global scope finds the original global x (value 5), which was never modified.

Question 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)
Show answer
This raises an UnboundLocalError. Python sees the assignment total = total + n and marks total as a local variable for the entire function. But then the right side tries to read total before it has been assigned locally — hence the error. To fix it, add global total as the first line inside the function. This tells Python to use the global total instead of creating a local one. The corrected code will print 5.

Question 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())
Show answer
The output is 1, 2, 3 (each on a separate line). The make_counter function creates a local variable count and returns the inner increment function. Thanks to nonlocal count, each call to increment modifies make_counter's count variable, which persists between calls because increment "closes over" it. This is an example of a closure — count survives even after make_counter has returned.

Question 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)
Show answer
The nonlocal keyword targets the enclosing scope (outer's local score), not the global one. So inner() changes outer's local score to 10, but that variable is destroyed when outer() returns. The global score remains 0. Fix 1: Replace nonlocal score with global score in inner() and remove the score = 0 line in outer(). Fix 2: Remove outer's local score entirely and use global score in inner(). The deeper lesson: always be clear about which scope you intend to target.

SUMMARY

Summary — Scope: Local, Global & nonlocal

Summary

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