Python Lambdas and Closures — Core Concepts
Lambdas and closures are two related ideas that become powerful when you treat functions as data.
- Lambda: compact syntax for an anonymous function expression
- Closure: function object plus the environment it captured from outer scope
Lambdas: Function Expressions
A lambda creates a function in expression form.
add = lambda a, b: a + b
print(add(2, 3)) # 5
Equivalent named function:
def add(a, b):
return a + b
The difference is mostly syntactic. Lambdas are limited to a single expression and are best for short, local logic.
Typical lambda use cases
key=arguments in sorting and grouping- quick transformations in
map/ comprehensions - callback arguments where a full named function would be noisy
records.sort(key=lambda r: (r["team"], -r["score"]))
Closures: Remembering Outer Variables
A closure appears when an inner function refers to variables from an enclosing function and survives after the outer function returns.
def make_power(exponent):
def power(base):
return base ** exponent
return power
square = make_power(2)
cube = make_power(3)
square and cube each carry their own captured exponent.
How It Works
Python resolves names using LEGB scope (Local, Enclosing, Global, Built-in). Closures use the Enclosing part.
When a nested function references an enclosing variable, Python stores that value binding in a cell object attached to the function (__closure__).
print(square.__closure__)
You rarely need to inspect this directly, but understanding it helps when debugging weird scope behavior.
Late Binding Gotcha
A frequent bug appears in loops:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2]
All lambdas refer to the same i, which ends at 2.
Fix by binding default arguments:
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
print([f() for f in funcs]) # [0, 1, 2]
Updating Captured State with nonlocal
Closures can maintain private mutable state.
def make_counter(start=0):
count = start
def inc():
nonlocal count
count += 1
return count
return inc
nonlocal says: use the enclosing scope variable, not a new local one.
Where Closures Shine
- factory functions that return configured behavior
- decorators and middleware patterns
- lightweight stateful helpers without defining a class
def threshold_filter(limit):
return lambda x: x >= limit
Closures vs Classes
Both can hold state.
Use closure when:
- state is tiny
- behavior surface is small
- object identity and many methods are unnecessary
Use class when:
- you need multiple related methods
- state model is rich or evolving
- explicit structure improves readability
Common Misconception
Misconception: lambda is faster than def.
Reality: runtime performance is usually similar. Choice should be about readability and maintainability.
Another misconception: closures copy values at definition time in all cases. They capture bindings, which is why late binding exists.
Connection to Related Topics
Closures pair naturally with Python Functions and Python Decorators. Many decorator implementations rely on nested functions that capture arguments from outer scope.
One Thing to Remember
Lambdas are concise function expressions, while closures are functions that carry captured scope, enabling configurable behavior without global state.
See Also
- Python Async Await Async/await helps one Python program juggle many waiting jobs at once, like a chef who keeps multiple pots moving without standing still.
- Python Basics Python is the programming language that reads like plain English — here's why millions of beginners (and experts) choose it first.
- Python Booleans Make Booleans click with one clear analogy you can reuse whenever Python feels confusing.
- Python Break Continue Make Break Continue click with one clear analogy you can reuse whenever Python feels confusing.
- Python Closures See how Python functions can remember private information, even after the outer function has already finished.