Closures
Discover how a nested function can remember variables from its surroundings long after the outer function has finished running.
Imagine you order a custom stamp at a print shop. You hand the clerk your initials, they carve the stamp, and then they hand it back to you. From that moment on, the stamp remembers your initials — even though the clerk and the print shop are long gone.
A closure works the same way. You call a function, it builds and returns a brand-new inner function, and that inner function remembers variables from the outer function — even after the outer function has finished running. This sounds magical at first, but it is one of Python's most useful and elegant tools.
See it in action
— step through the idea, then dive into the details below.Functions That Remember
A closure is an inner function that remembers variables from its outer function — even after that outer function has finished running. Think of it as a function carrying a little backpack of values wherever it goes.
#Functions Inside Functions
Before we talk about closures, let's get comfortable with the idea that functions can live inside other functions. Python allows this, and it is perfectly normal.
def outer():
message = "Hello from outer!"
def inner():
print(message) # inner can see outer's variable
inner() # call inner from inside outer
outer()So far, inner runs inside outer. That is neat, but not yet a closure. A closure happens when we return the inner function so it can be used outside — after outer has already finished.
#Your First Closure
def make_greeter(name):
def greet():
print(f"Hey, {name}!")
return greet # return the function itself, not greet()
say_hi = make_greeter("Alice")
say_hi() # make_greeter is long gone — but name is remembered!
say_bye = make_greeter("Bob")
say_bye()A closure is a backpack
When greet is created, Python quietly packs up the variables it needs — like name — into a little backpack. Wherever greet travels, the backpack comes with it. This backpack is the closure itself.
You can even peek inside: say_hi.__closure__[0].cell_contents returns 'Alice' — the value Python kept alive.
#Factory Functions — Making Configurable Functions
One of the most common uses of closures is building factory functions — functions that manufacture other functions, each pre-configured with a specific setting.
Here is a classic example: a multiplier factory.
def make_multiplier(n):
def multiply(x):
return x * n # n is captured from the enclosing scope
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 5 * 2
print(triple(5)) # 5 * 3
print(double(9)) # 9 * 2Notice we only write the multiplication logic once. Then we configure it differently by calling make_multiplier with different values. This keeps code DRY — Don't Repeat Yourself.
#State Without a Class — Using nonlocal
Closures can not only read captured variables — they can also update them, keeping state between calls. To do this, you need the nonlocal keyword, which tells Python: "I want to modify the variable from the outer scope, not create a brand-new local one."
def make_counter():
count = 0
def increment():
nonlocal count # without this, count would be a new local variable
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3Forgetting nonlocal causes an UnboundLocalError
If you write count += 1 without nonlocal count, Python sees the assignment and decides count is a local variable — but you never gave it a local value, so it crashes with UnboundLocalError: local variable 'count' referenced before assignment.
Rule of thumb: any time you assign to a captured variable (not just read it), you need nonlocal.
#Why Are Closures Useful?
Closures shine in several real-world situations:
- Configurable behavior —
make_multiplier,make_validator,make_logger. One template, many specializations. - Lightweight state — A counter, an accumulator, or a cache without writing a full class.
- Callbacks and event handlers — Pass a closure to a button click handler that already "knows" which item to act on.
- Building decorators — Decorators (the
@syntax you may have seen) are implemented using closures under the hood.
#The Connection to Decorators
Decorators are one of the most famous uses of closures. When you write @timer above a function, Python is really doing this:
``python my_function = timer(my_function) ``
timer is a function that accepts another function, wraps it in an inner function (a closure), and returns the wrapper. The wrapper closes over the original function.
def shout(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs) # func is captured from shout's scope
return result.upper()
return wrapper
@shout
def greet(name):
return f"hello, {name}"
print(greet("world"))Closures are the engine inside decorators
You do not need to master decorators to use closures, but knowing closures makes decorators feel obvious rather than magical. Once you are comfortable with make_multiplier, a decorator is just the same idea — a function that takes a function and returns a new, enhanced one.
#A Practical Example — Making a Validator
def make_range_checker(min_val, max_val):
def check(value):
if min_val <= value <= max_val:
return f"{value} is valid"
return f"{value} is OUT OF RANGE ({min_val}–{max_val})"
return check
check_age = make_range_checker(0, 120)
check_temp = make_range_checker(-50, 60)
print(check_age(25))
print(check_age(200))
print(check_temp(22))
print(check_temp(-99))What will the following code print? ```python def make_adder(x): def add(y): return x + y return add add5 = make_adder(5) print(add5(3)) ```
Key takeaways
- A closure is an inner function that **remembers variables from its enclosing scope** even after the outer function has finished.
- Factory functions like `make_multiplier(n)` use closures to create configurable, reusable functions without repeating logic.
- Use `nonlocal` when you need to **mutate** (not just read) a captured variable inside an inner function.
- Closures provide lightweight stateful behavior — like a counter — without needing to write a full class.
- Decorators are built on closures: a function that wraps another function and returns the wrapper is a closure pattern.
What does this code print?
def make_multiplier(n):
def multiply(x):
return x * n
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(4))
print(triple(4))This counter is supposed to print 1, then 2, then 3. What is wrong?
def make_counter():
count = 0
def increment():
count += 1
return count
return increment
counter = make_counter()
print(counter())
print(counter())
print(counter())Complete the factory function so it returns an inner function that adds n to any number.
def make_adder(n): def add(x): return x + add add10 = make_adder(10) print(add10(5)) # prints 15
Put these lines in the right order to build a greeting closure and print 'Hey, Maya!'
return greet
def make_greeter(name):
print(f"Hey, {name}!")say_hi = make_greeter("Maya")say_hi()
def greet():
Write a factory function called make_power that accepts an exponent n and returns a function that raises any number to that power. Then create square (n=2) and cube (n=3) from it, and test them with a few numbers. As a bonus challenge, add a call counter using nonlocal so each returned function tracks how many times it has been called, and prints the count alongside the result.
Try it live — edit the code and hit Run to execute real Python: