A lambda expression creates an anonymous function object. Defined in the Python Language Reference, it consists of the keyword lambda, a parameter list, a colon, and a single expression whose value is returned. Lambdas are syntactically restricted to one expression — no statements, assignments, or annotations.
Tiny anonymous functions
What you will learn: what a lambda is, how it relates to def, and that its body is a single expression whose value is returned automatically.
How to read this tab: Per PEP 8, do not assign a lambda to a name — use def instead. Lambdas are for inline use.
A lambda is a tiny function with no name, written in one line. lambda x: x * 2 is a function that doubles its input. You use lambdas when you need a small function briefly — usually to hand to another function — and naming it with def would be overkill.
PEP 8: do not assign a lambda to a name. If you are naming it (f = lambda x: ...), use def — it is more readable and gives a proper name in tracebacks. Lambdas exist to be anonymous and inline; the named examples here only show equivalence.
Lambda vs def
A lambda and a def create the same kind of object — a function. The difference is that a lambda is an expression (it can appear inline), has no name, and is limited to a single expression.
# These two are equivalent
def double(x):
return x * 2
double = lambda x: x * 2 # same behaviour, anonymous origin
print(double(5)) # 10 in both cases
# Lambdas can take multiple arguments
add = lambda a, b: a + b
print(add(3, 4)) # 7
# ...and default arguments
greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
print(greet("Priya")) # Hello, Priya!
print(greet("Arjun", "Namaste")) # Namaste, Arjun!
# The body is a single EXPRESSION whose value is returned automatically
# (no 'return' keyword, no statements allowed)
square = lambda x: x ** 2
print(square(6)) # 36If you are giving a lambda a name (double = lambda x: x*2), just use def instead — it is more readable and gives the function a proper name in tracebacks. The whole point of a lambda is to be anonymous and inline. The examples above assign names only to show equivalence.
✅ Beginner tab complete
- I can write a lambda with one or more arguments
- I know a lambda body is a single expression (no statements, no return keyword)
- I know lambda and def create the same kind of object
- I know not to assign a lambda to a name (use def instead)
Sort keys, map, filter, reduce
What you will learn: the main legitimate use (sort keys for sorted/min/max), and map/filter/reduce — plus when a comprehension is clearer.
How to read this tab: Default to comprehensions; reach for map/filter mainly with already-named functions.
Sort keys are the ideal lambda use. sorted(people, key=lambda p: p["age"]) is exactly what lambdas are for — a tiny throwaway function passed inline. For multi-level sorting, return a tuple: key=lambda w: (len(w), w).
The main use: sort keys
The single most common and most appropriate use of a lambda is as the key argument to sorted(), min(), max(), and list.sort() — telling them what to compare.
people = [
{"name": "Priya", "age": 30},
{"name": "Arjun", "age": 25},
{"name": "Rohit", "age": 35},
]
# Sort by age
by_age = sorted(people, key=lambda p: p["age"])
print([p["name"] for p in by_age]) # ['Arjun', 'Priya', 'Rohit']
# Sort by name length, then alphabetically (tuple key)
words = ["banana", "kiwi", "apple", "fig"]
print(sorted(words, key=lambda w: (len(w), w)))
# ['fig', 'kiwi', 'apple', 'banana']
# max / min with a key
print(max(people, key=lambda p: p["age"])["name"]) # Rohit
print(min(words, key=len)) # 'fig' (key=len, no lambda needed)
# Reverse sort
print(sorted([5, 2, 8, 1], key=lambda x: -x)) # [8, 5, 2, 1]
# (or simpler: sorted([5,2,8,1], reverse=True))Prefer comprehensions over map/filter + lambda. [x*2 for x in nums] reads better than map(lambda x: x*2, nums). Reach for map/filter mainly when you already have a named function to pass, like map(str, nums).
map, filter, and functools.reduce
nums = [1, 2, 3, 4, 5, 6]
# map — apply a function to every item
doubled = list(map(lambda x: x * 2, nums))
print(doubled) # [2, 4, 6, 8, 10, 12]
# filter — keep items where the function returns True
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens) # [2, 4, 6]
# reduce — fold a sequence into a single value
from functools import reduce
product = reduce(lambda acc, x: acc * x, nums)
print(product) # 720 (1*2*3*4*5*6)
# BUT: a comprehension is usually clearer than map/filter+lambda
doubled = [x * 2 for x in nums] # clearer than map
evens = [x for x in nums if x % 2 == 0] # clearer than filter
# Reach for map/filter mainly when you already HAVE a named function:
nums_as_str = list(map(str, nums)) # map with a builtin — clean[f(x) for x in xs] is generally more readable than map(lambda x: f(x), xs). Use a comprehension by default; use map/filter when passing an already-named function (e.g. map(str, nums)).[lambda: i for i in range(3)] gives three lambdas that all return 2 — they share the variable i. Fix with a default argument: [lambda i=i: i for i in range(3)].✅ Intermediate tab complete
- I can use lambda as a key= argument to sorted/min/max
- I can sort by a tuple key for multi-level sorting
- I know a comprehension is usually clearer than map/filter + lambda
- I can recognise the late-binding closure trap in a loop of lambdas
Syntactic restrictions and the function object
What you will learn: that a lambda is an ordinary function object with __name__ "
How to read this tab: Read to understand exactly why some things require def and produce clearer tracebacks.
Syntactic restrictions and the function object
A lambda produces exactly the same type of object as def — a function instance with __code__, __defaults__, and __closure__ attributes. The only differences are syntactic: a lambda's body must be a single expression (no statements, no return, no = assignment, no annotations), and its __name__ is always the string "<lambda>", which is why lambdas produce less helpful tracebacks than named functions.
f = lambda x, y=10: x + y
# Same object type as a def function
print(type(f)) # <class 'function'>
print(f.__name__) # '<lambda>' — always, hence poor tracebacks
print(f.__defaults__) # (10,)
print(f.__code__.co_argcount) # 2
# Lambdas capture by reference (closures) — the late-binding trap
funcs = [lambda: i for i in range(3)]
print([fn() for fn in funcs]) # [2, 2, 2] — all see final i
# Fix 1: default argument binds the CURRENT value
funcs = [lambda i=i: i for i in range(3)]
print([fn() for fn in funcs]) # [0, 1, 2]
# Fix 2: functools.partial
from functools import partial
def identity(i): return i
funcs = [partial(identity, i) for i in range(3)]
print([fn() for fn in funcs]) # [0, 1, 2]
# Conditional expression inside a lambda (allowed — it's an expression)
sign = lambda x: "positive" if x > 0 else "negative" if x < 0 else "zero"
print(sign(-5)) # negative
# What you CANNOT do in a lambda (all SyntaxError):
# lambda x: x = 5 # no assignment
# lambda x: print(x); x # no statement sequences
# lambda x: return x # no return keywordBecause the body is restricted to one expression, anything requiring a statement — a loop, a try, multiple steps, or an early return — cannot be a lambda and must be a def. The conditional expression (a if cond else b) is permitted because it is an expression, which lets lambdas express simple branching. For anything beyond a single clear expression, a named function is both required and more readable.
✅ Expert tab complete
- I know a lambda is the same object type as a def function
- I know lambda __name__ is always "
", giving poor tracebacks - I can fix late binding with a default argument: lambda i=i: i
- I know a conditional expression is allowed in a lambda but statements are not