Learn how Python closures work with simple real-world analogies, beginner-friendly code examples, and visual breakdowns. Master nonlocal
, scope levels, and practical use cases like function factories, private variables, and decorators.
The Key Idea
A closure happens when:
An inner function remembers the variables from the outer function even after the outer function has finished running.
Let’s walk through this with a real-world analogy
Code
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
times3 = make_multiplier(3)
print(times3(10)) # Output: 30
Think of it Like This:
Imagine:
You’re making a customized calculator.
- You call
make_multiplier(3)
, asking: “Hey, can you build me a multiplier that always multiplies by 3?” - Python says: “Sure! Here’s a custom function (called
multiplier
) that remembers the number3
you gave me.” - That custom multiplier gets saved into
times3
. - Later, when you run
times3(10)
: It remembers: “I was built withn = 3
, so I’ll return10 * 3
.”
This memory of n = 3
is what we call a closure.
Visualization
times3 = make_multiplier(3)
→ make_multiplier builds:
def multiplier(x):
return x * 3 ← "remembers n = 3"
→ returns multiplier → assigned to times3
Then:
times3(10) → 10 * 3 → 30
Even though make_multiplier()
has finished, its inner function multiplier()
keeps a snapshot of its environment, including the value n = 3
.
Closure = Inner Function + Remembered Values
So:
times3 = make_multiplier(3) # returns multiplier(x): return x * 3
Even though make_multiplier
is done executing, the times3
function it returned still remembers n = 3
.
Summary
- You create a function that returns another function.
- That returned function remembers the context in which it was created.
- This is closure.
Why we use closure-based functions
1. Preserve State Without Global Variables or Classes
Closures are great when you want a function to remember a value (like a configuration, multiplier, or counter) without using a global variable or creating a class.
Example: Create customized functions
def make_discount(percent):
def apply(price):
return price - (price * percent / 100)
return apply
student_discount = make_discount(10)
print(student_discount(200)) # 180.0
2. Create Factory Functions
You can use closures to generate specialized functions at runtime. This is a common design in function factories or decorators.
Example: Customized HTML wrapper
def html_tag(tag):
def wrap(text):
return f"<{tag}>{text}</{tag}>"
return wrap
h1 = html_tag("h1")
print(h1("Welcome")) # <h1>Welcome</h1>
3. Encapsulate Data (like private variables)
Closures let you hide data inside a function — acting like private members of a class.
Example: A counter with internal 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
4. Cleaner Alternative to Classes (for small behavior)
If you only need to encapsulate behavior and state for a simple case, closures are often lighter and cleaner than defining a class.
5. Functional Programming Patterns
Closures are used a lot in:
- Decorators
- Callbacks
- Event handling
- Functional APIs (like filters or strategies)
Summary
Use Case | Why Use Closure |
---|---|
Preserve configuration/state | Without using globals or classes |
Build specialized functions | e.g., customized formatters |
Encapsulate private variables | Secure internal state |
Functional programming | Clean and concise logic |
Create decorators | Add behavior dynamically |
So What Is nonlocal
?
🔑 Definition:
nonlocal
is a keyword that allows inner functions to modify variables from the enclosing (but non-global) scope.
Without nonlocal
, this line:
count += 1
Would create a new local variable count
inside the increment()
function — completely separate from the count = 0
in make_counter()
.
This would result in:
UnboundLocalError: local variable 'count' referenced before assignment
Scope Levels in Python (important!)
Python has 4 main scopes:
Level | Example |
---|---|
Local | Inside a function |
Enclosing | Outer function (like make_counter ) |
Global | Top-level script/module variables |
Built-in | print , len , etc. |
In your code:
count = 0
is in the enclosing scopenonlocal count
lets the inner functionincrement()
access and modify it
Visual Breakdown:
# EN CLOSING SCOPE
def make_counter():
count = 0 # <--- Outer variable, remembered by the closure
# INNER FUNCTION
def increment():
nonlocal count # <--- Declare we're using/modifying outer 'count'
count += 1
return count
return increment
Now each time you call counter()
, it uses and updates the same count
variable.
Without nonlocal
If you removed nonlocal
:
def make_counter():
count = 0
def increment():
count += 1 # ❌ Will raise UnboundLocalError
return count
Python would think you’re trying to create a new local variable count
, but it’s also being used before it’s assigned. That’s why nonlocal
is needed to tell Python:
“Don’t create a new variable — use the one from the enclosing scope.”
When to Use nonlocal
- You have a closure (a function inside another).
- The outer function has a mutable or changing variable.
- The inner function needs to update that variable over multiple calls.
Practice Creating Closure-Based Functions
See solved exercises here.
Let’s Connect!

If you enjoyed this and want more tutorials like it, follow me:
🎥 YouTube
👩💻 GitHub
💼 LinkedIn
📱 Instagram
📘 Facebook
Thanks so much for dropping by.