Course Progress71%
🍎 Python Advanced Python Topic 71 / 100
⏳ 8 min read

Closures

Inner functions that remember variables from their enclosing scope — even after the outer function has returned.

"A closure is a function with a memory. It remembers the variables from the scope where it was created, even after that scope is gone."

— ShurAI

The Scope Refresher

Before closures, recall that a function defined inside another function can see the outer function’s variables:

python
def outer():
    message = "Hello"       # lives in outer's scope

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

    inner()

outer()   # Hello

What Makes a Closure?

A closure happens when outer() returns the inner function. The inner function keeps a reference to the outer variable even after outer() has finished:

python
def make_greeter(greeting):
    def greet(name):
        print(f"{greeting}, {name}!")  # remembers 'greeting'
    return greet                      # return function, not greet()

# make_greeter() finishes here, but 'greeting' is kept alive
say_hi    = make_greeter("Hi")
say_hello = make_greeter("Hello")

say_hi("Riya")      # Hi, Riya!
say_hello("Arjun")  # Hello, Arjun!

# Each closure remembers its own 'greeting'
say_hi("Sneha")     # Hi, Sneha!
What a closure contains:
The function
The inner function’s code
Free variables
Outer variables it uses (captured)
The environment
The scope where those variables live

Closures as Counters

Closures can hold and update state using the nonlocal keyword:

python
def make_counter():
    count = 0                   # this variable is "closed over"

    def counter():
        nonlocal count          # tell Python: use outer 'count', not a local one
        count += 1
        return count

    return counter

c1 = make_counter()
c2 = make_counter()   # independent counter, own 'count'

print(c1())   # 1
print(c1())   # 2
print(c1())   # 3
print(c2())   # 1  — c2 has its own separate count
nonlocal vs global

nonlocal says "use the variable from the enclosing function's scope, not mine." global says "use the module-level variable." In closures, always use nonlocal when you need to modify the captured variable.

Closures vs Classes

A closure is often a lightweight alternative to a class when you only need to track a small amount of state:

python — closure approach (simpler)
def make_multiplier(factor):
    def multiply(n):
        return n * factor
    return multiply

double  = make_multiplier(2)
triple  = make_multiplier(3)
tenx    = make_multiplier(10)

print(double(5))   # 10
print(triple(5))   # 15
print(tenx(5))     # 50

Real Example — Logging with a Prefix

python
def make_logger(prefix):
    """Create a logger that always prepends a label."""
    def log(message):
        print(f"[{prefix}] {message}")
    return log

info    = make_logger("INFO")
warning = make_logger("WARNING")
error   = make_logger("ERROR")

info("Server started on port 8000")
warning("Disk usage above 80%")
error("Database connection failed")
output
[INFO] Server started on port 8000
[WARNING] Disk usage above 80%
[ERROR] Database connection failed

"Decorators are closures in disguise. Every time you write a decorator, you’re creating a closure — the wrapper function closes over the original function it receives."

— ShurAI

🧠 Quiz — Q1

What is a closure?

🧠 Quiz — Q2

In make_counter(), why do you need nonlocal count inside the inner function?

🧠 Quiz — Q3

You call double = make_multiplier(2) and triple = make_multiplier(3). What does double(5) return?

🧠 Quiz — Q4

What is the key thing a closure "closes over"?