Closures
Once you master closures, you unlock the ability to create custom function factories, stateful functions, decorators, event handlers, configuration-based logic, and real-world abstractions used in production systems.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • What lexical scope is and how Python resolves variable names
- • How closures capture and remember outer variables
- • Using nonlocal to modify closed-over state
- • Building configurable function factories using closures
- • Real-world patterns: counters, loggers, validators, auth middleware
Once you master closures, you unlock the ability to create:
- ✔ custom function factories
- ✔ stateful functions
- ✔ decorators
- ✔ event handlers
- ✔ configuration-based logic
- ✔ real-world abstractions used in production systems
This lesson takes you from theory → real project engineering.
🔥 1. The Core Idea: Lexical Scope
🏠 Real-World Analogy:
Think of lexical scope like a house with rooms. Each room (function) can see into the hallway (outer scope), but the hallway can't see into the rooms. Inner functions can "see" outer variables, but not vice versa.
Term
What It Means
Lexical Scope
Variable visibility is determined by where code is written , not where it runs
Inner Function
Can see variables from outer function ✅
Outer Function
Cannot see variables from inner function ❌
🔒 2. What Exactly Is a Closure?
🎒 The Backpack Analogy:
A closure is like a backpack that a function carries. When you create an inner function, it "packs" any variables it needs from the outer function. Even after the outer function is done, the inner function still has its backpack with all those values!
Step
What Happens
1. Nested function
A function is defined inside another function
2. Captures variables
The inner function uses variables from the outer function
3. Returned/passed out
The outer function returns the inner function
4. Remembers!
The inner function retains access to those captured variables forever
🧠 3. Why Closures Matter in Real Projects
- ✔ Keep state without classes Perfect for counters, caching, limits, tracking.
- ✔ Build custom configuration-based functions Used in Django, Flask, FastAPI, ML pipelines.
- ✔ Create decorators 100% closure-based.
- ✔ Clean, scalable architecture Reduces global variables and avoids bulky OOP when not necessary.
⚡ 4. Real Project Example #1 — A Counter Without Classes
⚠️ The nonlocal Keyword
When you want to modify (not just read) an outer variable, you MUST use nonlocal . Without it, Python thinks you're creating a NEW local variable!
Action
Needs nonlocal?
Reading outer variable: print(count)
No ✅
Modifying outer variable: count += 1
Yes! 🔑
💡 Why This Matters: Each counter has its own private count . This is used for tracking events, API call limits, unique ID generators, and session tracking.
⚙️ 5. Real Project Example #2 — A Configurable Logger
- ✔ Microservices
- ✔ Monitoring tools
- ✔ DevOps scripts
- ✔ Automated tests
🔐 6. Real Project Example #3 — Authentication Middleware
This is exactly how Flask decorators, FastAPI dependencies, permission systems, and API gateways work behind the scenes.
🚀 7. Real Project Example #4 — Custom Data Validators
- ✔ form validation
- ✔ Django & Flask forms
- ✔ database input checking
⚡ 8. Real Project Example #5 — Caching with Closure State
- ML predictions
- database-heavy functions
- expensive computations
- optimising backend requests
⚡ 9. Real Project Example #6 — Rate Limiting API Calls
- ✔ Discord bots
- ✔ payment gateways
- ✔ login protection systems
🧬 10. Real Project Example #7 — Dynamic Query Generators
Closures here enable ORM-like systems, flexible APIs, and dashboard filtering.
🔄 11. Function Composition Using Closures
This powers data pipelines, ML preprocessing, and functional programming styles.
🧩 12. Closures vs Classes — When to Use Which?
🤔 The Decision:
Both closures and classes can store state. But closures are lightweight (just a function), while classes are feature-rich (methods, inheritance, etc.). Choose based on complexity!
Use Closures When...
Use Classes When...
You need lightweight state (counter, cache)
You have complex data with many attributes
The behavior is more important than the data
You need inheritance or polymorphism
You want simple factories
You have many methods that interact
Performance matters (closures are faster)
You need reusable objects with identity
💡 Modern codebases often mix both. Use closures for quick utilities, classes for complex domains.
🧠 13. How Python Stores Closure Data
- __closure__ stores cell objects
- each cell contains captured variable values
- the closure survives even after the outer function ends
🔥 14. Common Mistakes (and How to Avoid Them)
❌ Mistake
✅ Fix
Missing nonlocal
UnboundLocalError
Add nonlocal variable_name
Capturing loop variable
All functions share last value
Use default argument: def f(i=i)
Using globals instead
Hard to test, not isolated
Use closure state instead
❌ Mistake #2: Loop variable capture (Tricky!)
🎯 15. Real-World Mini Project — Event Handler System
- ✔ a mini Pub/Sub system
- ✔ similar to Node.js EventEmitter
- ✔ used in GUIs, games, and backend events
You've mastered the basics. Now let's explore how senior engineers use closures in large-scale systems.
🔮 16. Using Closures for Dependency Injection
Most dependency injection systems in other languages require containers, service providers, and registries. Python can do it with one function.
Used in microservices, test environments, feature-flagged deployments, and plugin systems.
⚡ 17. Closures for Middleware (Flask, FastAPI, Starlette)
This is how FastAPI Dependency Injection, Flask Decorators, Django Middleware, and Starlette Routing all work internally.
🎛 18. Closures to Build Retry, Timeout, Backoff Systems
Cloud-based systems (AWS, GCP, Stripe, PayPal, Twilio) ALL use retry + exponential backoff to prevent failures.
📦 19. Closures for Local Caching With Expiration
Used in ML inference servers, recommendation systems, data dashboards, and pricing engines.
🧠 20. Using Closures to Build Feature Flags (A/B Testing)
This mirrors real A/B testing systems at Netflix, Facebook, and Shopify.
🧬 21. Closures for Analytics Tracking
Used in Mixpanel, Firebase Analytics, and Amplitude.
🧩 22. Using Closures to Build Mini Frameworks
Frameworks like Flask, FastAPI, Click, and Typer are closure-heavy.
Closures → registry → framework. You just built something similar to CLI libraries, routing systems, and plugin engines.
🛠 23. Function Pipelines Using Closures
Used by Pandas, Spark, ML preprocessing, and data validation systems.
⚙️ 24. Closures for Automatic Resource Cleanup
Used with database connections, file streams, and cache layers.
🧪 25. Closures for Test Fixtures
🌀 26. Closures for GUI & Game Event Systems
Closures store level states, UI states, and game event metadata.
🔍 27. Debugging Closures in Large Systems
Useful for debugging decorators, factories, async pipelines, and cached layers.
⚠️ 28. The "Late Binding" Bug & How to Fix It
This is one of the most common closure bugs in the world.
⚡ 29. When NOT to Use Closures
- ✘ too much state
- ✘ too many layers of wrapping
- ✘ juniors need to maintain it
- ✘ class-based structure is simpler
- ✔ OOP-heavy systems
- ✔ complex entities
- ✔ long-lived objects
- ✔ inheritance-heavy architectures
You've expanded your closure knowledge. Now let's explore expert-level patterns used in AI pipelines, backends, and production systems.
⚡ 30. Async Closures — Combining AsyncIO + Lexical Scope
Used in websocket reconnection, unstable network fetches, async microservice calls, and task orchestration tools.
🧠 31. Building a Closure-Based State Machine
This pattern runs boss AI in games, dialogue systems, backend workflow state, and user authentication flow.
🚀 32. Closure-Driven ML Pipelines
This powers preprocessing, augmentation, feature engineering, and batch transforms.
🔍 33. Closures for Compiler-Style Token Processing
This mimics syntax highlighters, linting engines, formatters, and interpreters.
🕸 34. Microservice Routing Using Closure Factories
This pattern appears in Flask, FastAPI, Node.js Express equivalents, and API gateways.
⏳ 35. Task Scheduling System (Cron-like)
Used for price updates, leaderboard refresh, background jobs, and monitoring tasks.
🧬 36. Building a Custom ORM Layer Using Closures
This allows dynamic model creation, field injection, serialization/deserialization, and validation.
🔄 37. Declarative UI Logic (React-like) With Closures
Used in game UIs, terminal apps, custom dashboards, and educational tools.
🔐 38. Building Permission Systems Using Closure Capture
This powers admin dashboards, e-commerce backends, and authentication gateways.
📦 39. Closure-Based Message Queues
Used for simulation, job queues, event systems, and async workers.
🧮 40. Mathematical Function Generators
Used in physics simulation, rendering engines, machine learning, and game movement curves.
🧩 41. Partial Application (Custom Implementation)
Alternate to functools.partial, giving Python the power of functional programming and cleaner callbacks.
🎛 42. "Middleware Stack" Engine Using Closures
Used in web servers, request filtering, AI agent chains, and on-device pipelines.
👁 43. Closures for Observers / Watchers (Reactive Programming)
Used in UI systems, stock trackers, game events, and reactive dashboards.
🧬 44. Closure-Based Memoization With Custom Invalidation
Better than lru_cache when you need dynamic TTL, external invalidation, or distributed system caching.
You now understand the deepest real-world closure techniques, used in:
- ✔ AI pipelines
- ✔ backend microservices
- ✔ ML preprocessing
- ✔ schedulers
- ✔ state machines
- ✔ frameworks
- ✔ middleware systems
- ✔ dependency injection
- ✔ rate limiters
- ✔ caching systems
- ✔ permission systems
- ✔ ORM layers
- ✔ reactive programming
- ✔ message queues
You've reached expert-level closure mastery used by senior Python engineers in production systems.
📋 Quick Reference — Closures
Pattern
What it does
🏆 Lesson Complete!
You now understand how closures capture state and how Python resolves variable scope — a key skill behind decorators, factories, and callback systems.
Up next: Context Managers — control resource lifecycle with the with statement.
Practice quiz
What is lexical scope?
- Variable visibility decided by where code RUNS
- A type of global variable
- Variable visibility decided by where code is WRITTEN
- A way to import modules
Answer: Variable visibility decided by where code is WRITTEN. Lexical scope means visibility is determined by where code is written, not where it runs.
What is a closure?
- An inner function that remembers variables from its enclosing scope
- A function that takes no arguments
- A way to close a file
- A built-in Python keyword
Answer: An inner function that remembers variables from its enclosing scope. A closure is an inner function that captures and remembers variables from its outer function.
Which keyword lets an inner function MODIFY a variable from the enclosing function?
- global
- static
- extern
- nonlocal
Answer: nonlocal. nonlocal tells Python to modify the outer (enclosing) variable instead of creating a new local one.
Given make_multiplier(factor) returning multiply(x)=x*factor, what does make_multiplier(10)(3) return?
- 13
- 30
- 10
- 3
Answer: 30. factor=10 is captured, so multiply(3) returns 3 * 10 = 30.
What happens if you do count += 1 inside an inner function WITHOUT declaring nonlocal count?
- It raises an UnboundLocalError
- It works fine
- It modifies a global
- It returns None
Answer: It raises an UnboundLocalError. Without nonlocal, Python treats count as a new local, so reading it before assignment raises UnboundLocalError.
Given compose(f, g) returning f(g(x)), with double(x)=x*2 and add_5(x)=x+5, what does compose(double, add_5)(10) print?
- 25
- 20
- 30
- 15
Answer: 30. g runs first: add_5(10)=15, then double(15)=30.
What does [lambda: i for i in range(3)] then [f() for f in funcs] print (the late-binding bug)?
All lambdas share the same i, which ends at 2 after the loop, so every call returns 2.
How do you fix the loop-variable capture bug?
- Use nonlocal
- Use global
- Use a tuple
- Use a default argument like lambda i=i: i
Answer: Use a default argument like lambda i=i: i. lambda i=i: i captures the current value of i as a default, giving each lambda its own copy.
Where does Python store a closure's captured variables?
- In __dict__
- In the function's __closure__ cell objects
- In a global registry
- In the stack
Answer: In the function's __closure__ cell objects. Captured variables live in cell objects accessible via the function's __closure__ attribute.
Given a polynomial factory f(x)=a*x*x+b*x+c made with polynomial(1, -3, 2), what does the function return for x=2?
- 2
- 4
- 0
- -3
Answer: 0. 1*4 + (-3)*2 + 2 = 4 - 6 + 2 = 0.