DocsHub
Functions

Scope

Learn how Python finds variable names using local, global, nonlocal, and the LEGB rule.

Scope

Scope is the idea that a variable does not exist everywhere in your program — it only exists in certain places. Where you create a variable determines where you can use it.

Think of it like rooms in a house. A notebook in your bedroom is only accessible in your bedroom. To use it in the kitchen, you would have to carry it out. Python works the same way — variables created inside a function stay inside that function unless you deliberately bring them out.


Local scope

A variable created inside a function is local to that function. It only exists while the function is running and disappears when it finishes.

def greet():
    message = "Hello!"   # local variable
    print(message)

greet()          # Hello!
print(message)   # NameError — message does not exist out here

message was born inside greet() and dies when greet() finishes. The outside world cannot see it.

Two functions can have local variables with the same name — they do not interfere with each other:

def func_a():
    x = 10
    print(x)   # 10

def func_b():
    x = 99
    print(x)   # 99

func_a()
func_b()

Each x lives in its own function. They are completely separate.


Global scope

A variable created at the top level of your file — outside any function — is global. It is accessible everywhere.

name = "Ali"   # global variable

def greet():
    print(f"Hello, {name}!")   # can read the global variable

greet()        # Hello, Ali!
print(name)    # Ali

Reading a global variable from inside a function is fine. But modifying it is where things get tricky:

count = 0

def increment():
    count += 1   # UnboundLocalError

increment()

This fails. When Python sees count += 1 inside the function, it assumes count is a local variable — but it was never assigned locally, so it does not exist yet.

To modify a global variable inside a function, you must declare it with the global keyword:

count = 0

def increment():
    global count
    count += 1

increment()
increment()
print(count)   # 2

global count tells Python — do not create a new local variable, use the one that already exists in the global scope.

Using global is generally a sign that your code could be better structured. It creates hidden dependencies between functions and the global state, which makes bugs harder to track. Prefer passing values as arguments and returning results instead.


Enclosing scope

When you define a function inside another function, the inner function can access variables from the outer function. This is called enclosing scope.

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

    def inner():
        print(message)   # can access outer's variable

    inner()

outer()   # Hello from outer

message is not global — it belongs to outer(). But inner() can still read it because it is enclosed inside outer().


nonlocal — modifying an enclosing variable

Just like global, if you want to modify a variable from the enclosing function, you need to declare it with nonlocal:

def outer():
    count = 0

    def inner():
        nonlocal count
        count += 1
        print(f"count is now {count}")

    inner()
    inner()
    inner()

outer()

Output:

count is now 1
count is now 2
count is now 3

Without nonlocal, count += 1 would try to create a new local variable inside inner() and fail with an UnboundLocalError.


The LEGB rule

When you use a variable name, Python searches for it in a specific order — Local, Enclosing, Global, Built-in. This is called the LEGB rule.

Not found Not found Not found Not found Found Found Found Found Variable used L — Local scopeInside the current function E — Enclosing scopeOuter function if nested G — Global scopeTop level of the file B — Built-in scopePython's built-in names NameError Use it

Python starts at the innermost scope and works outward. The first place it finds the name, it uses it. If it reaches the end without finding it, you get a NameError.


L — Local

The current function's own variables:

def greet():
    name = "Ali"    # local
    print(name)     # found in L — used immediately

E — Enclosing

The outer function's variables when functions are nested:

def outer():
    name = "Ali"    # enclosing

    def inner():
        print(name)  # not in L, found in E
    
    inner()

G — Global

The top level of the file:

name = "Ali"    # global

def greet():
    print(name)  # not in L, not in E, found in G

B — Built-in

Python's own built-in names like print, len, range, type:

def greet():
    print(len("Ali"))  # print and len found in B

You never define print or len yourself — Python provides them in the built-in scope.


Shadowing — when local hides global

If you create a local variable with the same name as a global one, the local one shadows the global inside that function:

name = "Global Ali"

def greet():
    name = "Local Ali"   # shadows the global
    print(name)          # Local Ali

greet()
print(name)              # Global Ali — global is untouched

Inside greet(), Python finds name in local scope (L) and never looks further. The global name is hidden but not changed.


A real example

A counter that uses enclosing scope to keep state:

def make_counter():
    count = 0

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

    return increment

counter = make_counter()

print(counter())   # 1
print(counter())   # 2
print(counter())   # 3

count lives in the enclosing scope of make_counter(). Every time you call counter(), it increments that same variable. This pattern is called a closure — the inner function remembers the environment it was created in. You will see this used heavily in decorators.


Summary

ScopeWhereKeyword to modify
LocalInside the current function
EnclosingOuter function when nestednonlocal
GlobalTop level of the fileglobal
Built-inPython's own names
  • Python searches L → E → G → B in that order
  • Reading a global variable is fine — modifying it needs global
  • Modifying an enclosing variable needs nonlocal
  • Avoid global when you can — pass values as arguments instead

On this page