🚀 Python Closures: The Magical Backpack of Functions

You’ve stepped into one of the most fascinating and mind-bending corners of Python! 🌀 Today, we’ll explore Closures — those clever, self-contained mini-tools that remember things even when their creator is long gone.
Think of closures as enchanted backpacks 🎒 that carry a little memory of their maker — ready to be opened anytime, anywhere.
🧠 What Exactly Is a Closure? (The Backpack Trick)
Let’s imagine you pack a backpack with some snacks 🍫 and a notebook 📓. You zip it up and hand it to a friend. Even after you leave, your friend can still open the backpack and enjoy what you packed inside.
That’s exactly what happens in a Python closure.
🎯 In code terms:
- The backpack → an inner function (a function defined inside another function).
- The snacks → variables from the outer function’s scope.
- The friend → the inner function that still remembers those variables after the outer function finishes running.
Let’s unpack that with code.
💻 A Simple Python Closure Example
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
Here’s what’s happening:
outer()is our backpack-packer.- Inside it, we define
count = 0. - The
inner()function adds 1 tocountand returns it. - But instead of calling
inner()insideouter(), we return it — handing off the backpack!
Now watch the magic:
counter = outer()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
Even though outer() has finished running, the counter() function remembers the count variable.
That memory — that connection — is what makes it a closure.
🤖 A Closure is a Function with a Memory
Definition: A closure is a function that:
- Is defined inside another function,
- References variables from that enclosing function,
- And remembers those variables even after the outer function has finished executing.
Closures are like tiny data-holding machines — each one carrying its own state.
🎮 Real-Life Example: A Game Score Tracker
Suppose you’re building a small game and want to track players’ scores — but you don’t want to use global variables. Closures are perfect for this!
def create_score_tracker():
score = 0
def add_points(points):
nonlocal score
score += points
return score
return add_points
Usage:
player1 = create_score_tracker()
print(player1(10)) # 10
print(player1(5)) # 15
player2 = create_score_tracker()
print(player2(7)) # 7
Each player gets their own private closure, with their own score safely tucked inside.
No interference. No globals. Just clean, self-contained logic.
🛑 When nonlocal Doesn’t Play Nice
Sometimes, especially in older interpreters or limited environments (like some online Python playgrounds), you might run into this:
SyntaxError: no binding for nonlocal 'count' found
That usually happens if:
- The variable isn’t in the immediate outer scope, or
- The environment doesn’t handle
nonlocalproperly.
🩹 Quick Fix: Use a Mutable Object (like a list)
def outer():
count = [0]
def inner():
count[0] += 1
return count[0]
return inner
Now:
counter = outer()
print(counter()) # 1
print(counter()) # 2
The list works because it’s mutable — meaning we can change its content without redefining it.
🧃 Reading vs. Updating Outer Variables
You can read outer variables inside a closure just fine:
def outer():
name = "Ekene"
def inner():
return "Hello, " + name
return inner
But if you try to change that variable:
def outer():
name = "Ekene"
def inner():
name += " the Coder" # ❌ Error!
return name
return inner
You’ll get:
UnboundLocalError: local variable 'name' referenced before assignment
Python assumes you’re trying to create a new local variable called name — not modify the outer one.
🧩 Why nonlocal Matters
When you add nonlocal, you’re telling Python:
“Hey, I don’t want a new local variable — I want to modify the one from the outer function.”
Without nonlocal:
def outer():
count = 0
def inner():
count += 1 # ❌ Error
return count
return inner
With nonlocal:
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
Now the closure works perfectly — and your function remembers and updates its state each time it’s called.
🧭 Summary
Closures let you create functions that remember things — a perfect mix of data and behavior in one neat package. They make your code elegant, reusable, and delightfully self-contained.
If you ever feel like your functions are forgetting too much, hand them a backpack — give them a closure! 🎒💡
📝 Review & Practice — Fill in the Gaps
- A closure is a function that ____ and ____ even after the outer function finishes running.
- In a closure, the outer function’s variables act like items packed into a ____.
- The
nonlocalkeyword allows the inner function to ____ variables from the outer function. - If you try to modify an outer variable without using
nonlocal, Python raises a ____ error. - True or False: A closure can remember its outer function’s variables even after that outer function is gone.
- Write a function
create_timer()that returns another function which adds 1 second each time it’s called, without usingnonlocal. - Why might we replace an integer with a list in a closure? (Hint: mutability!)
-
What will this code print?
def outer(): name = "Ekene" def greet(): return "Hello, " + name return greet say_hello = outer() print(say_hello()) - When do we use
nonlocalinstead ofglobal? - In your own words, describe one real-world situation where you could use closures.