Lambda Functions in Python: Anonymous One-Liners and Where They Shine
lambda args: expressionreturns a function object equivalent to a one-linedef.- The most common use case is as a
key=argument tosorted(),min(), andmax(). - Lambdas cannot contain statements, assignments, or multiple expressions.
- When the body gets complex or reusable, replace the lambda with a named function.
The lambda syntax
A lambda creates an anonymous function in a single expression. The keyword is followed by a comma-separated parameter list, a colon, and exactly one expression whose value is automatically returned:
- Write the
lambdakeyword. - List zero or more parameter names, separated by commas.
- Write a colon (
:). - Write a single expression — this is the implicit return value.
# Equivalent pairs
# Named function
def square(x):
return x * x
# Lambda
square = lambda x: x * x
print(square(5)) # 25
# Multi-argument lambda
add = lambda a, b: a + b
print(add(3, 4)) # 7
# No-argument lambda (unusual but valid)
greeting = lambda: "Hello!"
print(greeting()) # Hello!
Assigning a lambda to a name like square = lambda x: x * x is technically valid but stylistically discouraged. If you are naming the function, use def — it gives you a proper docstring slot and a readable __name__. Lambdas shine when they are used inline and discarded immediately.
Sorting with key=lambda
The key parameter of sorted(), list.sort(), and similar functions accepts any callable that maps a single element to its sort key. Lambdas let you write that mapping inline without declaring a separate function:
people = [
{"name": "Carol", "age": 32},
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 29},
]
# Sort by age
by_age = sorted(people, key=lambda p: p["age"])
print(by_age[0]["name"]) # Alice
# Sort by name length, then alphabetically
by_name_len = sorted(people, key=lambda p: (len(p["name"]), p["name"]))
For attribute access on objects, operator.attrgetter("attr") is a faster alternative. For dictionary key access, operator.itemgetter("key") avoids the lambda entirely and is slightly more efficient for large datasets.
min(), max(), and lambda
min() and max() accept the same key= argument, which lets you find extremes based on a derived value without pre-sorting:
products = [
("Widget", 9.99),
("Gadget", 24.50),
("Doohickey", 4.75),
]
cheapest = min(products, key=lambda item: item[1])
print(cheapest) # ("Doohickey", 4.75)
longest_name = max(products, key=lambda item: len(item[0]))
print(longest_name) # ("Doohickey", 4.75)
Conditional expressions inside lambdas
Because Python's ternary operator (value_if_true if condition else value_if_false) is an expression, not a statement, it fits inside a lambda body:
clamp = lambda x, lo, hi: lo if x < lo else (hi if x > hi else x)
print(clamp(5, 0, 10)) # 5
print(clamp(-3, 0, 10)) # 0
print(clamp(15, 0, 10)) # 10
When conditional logic starts nesting like this, the readability cost of a lambda is usually not worth it. A named function with explicit if / elif / else is far easier to read and test.
map() and filter()
map(func, iterable) applies a function to every element; filter(func, iterable) keeps elements for which the function returns a truthy value. Both return lazy iterators:
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # [2, 4, 6, 8, 10, 12, 14, 16]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8]
In modern Python, list comprehensions and generator expressions tend to replace most map() and filter() calls because they read left-to-right in the natural order of thought: [x * 2 for x in numbers] and [x for x in numbers if x % 2 == 0]. The functional forms remain useful when passing a callable object rather than inline logic.
What lambdas cannot do
A lambda body must be a single expression. The following are all illegal inside a lambda and require a def instead:
# NOT valid in a lambda:
# Assignments
# lambda x: result = x * 2 # SyntaxError
# Statements (if/for/while/try)
# lambda x: if x > 0: return x # SyntaxError
# Multiple expressions
# lambda x: x * 2; x + 1 # SyntaxError
# Walrus operator (:=) works, but rarely helps readability
process = lambda x: (y := x * 2, y + 1)[1]
Lambdas also cannot have type annotations on their parameters, they produce unhelpful <lambda> tracebacks, and they cannot carry a docstring. These limitations are by design — if you need any of those things, use def.
lambda vs def: a decision guide
| Criterion | Use lambda | Use def |
|---|---|---|
| Will it be named and reused? | No | Yes |
| Body complexity | Single short expression | Multiple lines or statements |
| Needs type hints? | No | Yes |
| Needs a docstring? | No | Yes |
| Used as key= in sorted/min/max | Ideal | Overkill |
| Needs clear traceback name? | No | Yes |
f = lambda x: ..., rename it to a proper def f(x): ... block. The lambda form saves one line at the cost of inspectability, testability, and clarity.