A lambda is a tiny anonymous function — a function with no name, written as a single expression. Use it when you need a quick function right here, right now, especially as an argument you hand to another function:
square = lambda x: x * x
print(square(5)) # 25
Read it as lambda parameters: expression. The expression is evaluated and returned automatically — there is no return keyword, because there is nowhere to put one. That lambda is exactly equivalent to the definition below; the only thing it lacks is a name:
def square(x):
return x * x
Anything you can do with a lambda you can do with a def. Lambdas exist for brevity: for a one-line transformation passed straight into another function, a named definition can feel like needless ceremony.
flowchart LR I["[1, 2, 3]"] --> L["lambda n: n * 2"] --> O["[2, 4, 6]"] style L fill:#3776ab,color:#fff
Inline arguments
Lambdas shine as inline arguments. The classic case is a key for sorted(), where you only need the function once:
names = ["Bo", "Alexandra", "Cy"]
by_length = sorted(names, key=lambda name: len(name))
print(by_length) # ['Bo', 'Cy', 'Alexandra']
map and filter also take a function and a sequence, so a lambda slots right in:
nums = [1, 2, 3, 4]
doubled = list(map(lambda n: n * 2, nums)) # [2, 4, 6, 8]
evens = list(filter(lambda n: n % 2 == 0, nums)) # [2, 4]
Often a list comprehension does the same job more clearly — [n * 2 for n in nums]. Reach for a comprehension when it reads better (see the Comprehensions lesson).
names = ["Bo", "Alexandra", "Cy"] print(sorted(names, key=lambda name: len(name))) nums = [1, 2, 3, 4] print(list(map(lambda n: n * 2, nums))) print(list(filter(lambda n: n % 2 == 0, nums))) combine = lambda x, y: x + y print(combine(3, 4))
Lambdas capture their surroundings
A lambda can use variables from the code around it, not just its own parameters. When you write lambda x: x * factor, the name factor is captured from the enclosing scope and remembered for later. That turns a lambda into a tiny factory: a function that builds and returns a lambda customized with a value.
Capturing has one famous sharp edge inside loops. The list [lambda: i for i in range(3)] builds three lambdas, but every one of them reads the same variable i. They do not snapshot the value — they look the name up later, by which point the loop has finished and i is 2. So all three return 2, never 0, 1, 2. The figure below shows why.
flowchart LR F1["fn 0"] --> I["loop variable i"] F2["fn 1"] --> I F3["fn 2"] --> I I --> R["i is 2 by the time any fn runs"] style I fill:#b45309,color:#fff style R fill:#3776ab,color:#fff
# Each lambda reads i LATER, when i is already 2. buggy = [lambda: i for i in range(3)] print([f() for f in buggy]) # [2, 2, 2] # A default argument captures i's value at definition time. fixed = [lambda i=i: i for i in range(3)] print([f() for f in fixed]) # [0, 1, 2]
When a def is the better choice
A lambda is a single expression with no name, no statements, and no return. The moment your logic needs more than that — a conditional with several branches, a helper variable, a loop, or a docstring — reach for a real def. The name itself is documentation: def distance(p1, p2) tells the next reader what it does, while a bare lambda a, b: ... forces them to reverse-engineer the intent.
A useful habit is to keep lambdas short and local. If you assign one to a meaningful name and never pass it anywhere, that is usually a def in disguise — write it that way and your meaning becomes obvious to everyone who follows.
Write make_multiplier(factor) that returns a lambda which multiplies its input by factor. So make_multiplier(3) returns a function, and calling that function on 10 gives 30.
def make_multiplier(factor):
# return lambda x: ...
pass
A lambda can use
factorfrom the enclosing function — it is captured.Return
lambda x: x * factor.
def make_multiplier(factor):
return lambda x: x * factor
Write sort_by_last(words) that returns words sorted by each word's last character. Use a lambda as the key.
def sort_by_last(words):
# return sorted(words, key=lambda ...)
pass
sorted(words, key=...) needs a function that returns the sort key for each item.
The last character of a string w is w[-1].
def sort_by_last(words):
return sorted(words, key=lambda w: w[-1])
What does this print? A lambda doubles each number via map.
nums = [1, 2, 3] doubled = list(map(lambda n: n * 2, nums)) print(doubled)
map applies the lambda to 1, then 2, then 3.
2, 4, 6 — list() prints them with brackets and commas.
This lambda won't even parse — return is illegal inside a lambda body. Fix it so sign(n) returns "pos" when n >= 0 and "neg" otherwise. Use a single conditional expression (a if cond else b).
sign = lambda n: return "pos" if n >= 0 else "neg"
A lambda returns its expression automatically — drop the
returnkeyword.The ternary
"pos" if n >= 0 else "neg"is a single expression, legal in a lambda.
sign = lambda n: "pos" if n >= 0 else "neg"
Write make_counters() that returns a list of three zero-argument functions — the first returns 0, the second 1, the third 2. A naive [lambda: i for i in range(3)] has the late-binding bug (all return 2); capture each value with a default argument.
def make_counters():
# return [lambda ... for i in range(3)] -- bind i so each fn keeps its own value
pass
The bug is that every lambda reads the same
iafter the loop ends.Give each lambda its own snapshot with a default argument:
lambda i=i: i.
def make_counters():
return [lambda i=i: i for i in range(3)]
The ternary: a conditional as one expression
Because a lambda body is a single expression, you cannot write an if statement inside it. You can still make choices, using Python's conditional expression — often called the ternary. It reads as value_if_true if condition else value_if_false, and the whole thing is one expression with a value:
parity = lambda n: 'even' if n % 2 == 0 else 'odd'
print(parity(4)) # even
The ternary evaluates the condition, then yields exactly one of the two branches. It works anywhere an expression is allowed — inside a return, inside a list, and inside a lambda. Reserve it for genuinely two-way choices; a long chain of nested ternaries quickly becomes harder to read than a plain if.
Sorting on more than one key
A key function can return any value Python knows how to compare, including a tuple. Return a tuple and sorted() compares element by element — first by the first element, then by the second to break ties. That is how you sort on more than one criterion with a single lambda:
people = [('Ada', 90), ('Lin', 90), ('Bo', 70)]
order = sorted(people, key=lambda p: (-p[1], p[0]))
print(order) # [('Ada', 90), ('Lin', 90), ('Bo', 70)]
The minus sign on the score sorts it descending while the name breaks ties ascending. Tuples compared this way are the standard trick for multi-key sorts, and a lambda is the cleanest way to express the key.
Lambdas are a convenience, not a requirement
It is worth saying plainly: you can write entire, excellent programs without a single lambda. Every lambda has an exact def equivalent, and the def is often more readable because it carries a name and a docstring. Lambdas earn their place in two spots — as a short key or callback handed straight into another function, and in quick interactive experiments where typing a full definition breaks your flow.
If you ever feel a lambda is the only way to solve a problem, that feeling is almost always wrong. Reach for the tool that makes the line readable; most of the time, that tool is a named function.
Recap
- A lambda is
lambda params: expression— one expression, returned automatically, with noreturn. - It shines as an inline argument: the
keyforsorted, or a function formapandfilter. - Lambdas capture outer variables by name, which causes late-binding bugs inside loops; bind a value with a default argument like
lambda i=i: i. - When the logic needs statements, branches, or a name, write a
definstead.
Next you'll see how comprehensions can replace many map and filter calls with something even more readable.