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."
— ShurAIThe Scope Refresher
Before closures, recall that a function defined inside another function can see the outer function’s variables:
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:
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!
Closures as Counters
Closures can hold and update state using the nonlocal keyword:
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 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:
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
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")
[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"?