A lambda is a one-line, anonymous function written inline at the spot where you need it. It's the right tool for tiny, throwaway functions, especially the small helper you pass to sorted, min, or max to tell it how to compare items. This lesson covers the syntax, what lambda can and can't do, where it actually shines, and the anti-patterns to avoid.
lambda SyntaxA lambda is an expression that evaluates to a function. The shape is fixed:
Three pieces matter. The lambda keyword starts it. Then a comma-separated parameter list (no parentheses around it). Then a colon. Then a single expression whose value becomes the return value. There's no return keyword and no function body, just the expression.
Both double and greet are functions, the same kind of object you'd get from def. They just happen to have been built with a one-line expression-only syntax. Calling them works exactly like any other function: parentheses, arguments, return value.
The body has to be a single expression. An expression is something that produces a value: n * 2, f"Hi, {name}", a + b if a > b else b + a, a function call, a comprehension. Statements like if, for, while, return, =, print(...) as a side-effecty line, are not expressions in the same sense and can't go inside a lambda.
A lambda can take any number of parameters, including zero:
The first lambda takes no parameters, so its parameter list is empty. The second takes two. The third takes three. The colon always separates the parameter list from the body.
Because a lambda is an expression, you can use it in places where a function name doesn't fit naturally. The most common one is passing a function as an argument:
The lambda p: p * 1.1 is created on the spot, handed to map, and never given a name. That's the throwaway-function pattern in its purest form.
The body of a lambda can use a conditional expression (x if condition else y), which is the ternary form of if/else. That's still a single expression, so it's allowed:
Note the conditional expression: price * 0.9 if member else price. It's one expression that picks between two values, not an if statement. You'll see this often in lambdas because it's the only branching tool the syntax allows.
A lambda's body is one expression. That single constraint rules out a lot:
return keyword. The expression's value is the return value, so writing return would be redundant. Putting return inside a lambda is a SyntaxError.=. lambda n: x = n is a SyntaxError. (The walrus operator := is technically an expression and does work, but it's almost always the wrong tool inside a lambda.)if, for, while, try, with, or def. These are statements. The conditional expression (x if cond else y) is allowed because it's an expression, not a statement.lambda x: int = 0: x + 1 is a SyntaxError. If you want type hints, use def.The fix is to drop return; the expression itself is the return value:
The "single expression" rule is the line that decides when lambda is the right tool and when you need def. If your function fits on one line as an expression, a lambda may be a fine choice. The moment you want a second line, a local variable, an if statement, or a try/except, you've outgrown lambda and should use def.
Cost: A lambda has the same call cost as a function defined with def. There's no speed advantage to one over the other. The choice between them is about readability, not performance.
A common stumble is reaching for a lambda when the logic is mildly complex, then squashing it into one expression with chained conditionals. The result is technically valid but hard to read:
What's wrong with this code?
The lambda works, but it's three levels of nested if/else packed into one line. Anyone reading this has to parse the chain mentally before they can use it. A regular function is much clearer:
Fix:
The def form reads top to bottom, mirrors the natural way you'd describe the rules out loud, and leaves room for a docstring if the function grows. The rule of thumb is "if the lambda body has more than one operator, ask whether def would be clearer". Usually it is.
There's a small set of places where lambdas actually shine, and a much larger set of places where they don't. Here's the honest list of the good ones.
1. As the `key` argument to `sorted`, `min`, `max`, and `list.sort`. This is by far the most common real use. The function you need is a tiny "extract this field" helper, and giving it a name above the call is just noise.
The lambda item: item["price"] is exactly the kind of thing a lambda was made for: a function that exists only for the duration of this sorted call.
2. As callbacks in a UI framework. When you wire a button to a small action, a lambda is often shorter than a named handler and lives right next to the widget it belongs to.
3. Inside `filter`, `map`, and a few other higher-order built-ins. These take a function as an argument, and a lambda fits the "function I'm only using once" shape well. (Comprehensions are often cleaner; we'll come back to that.)
The lambda answers "should this product be kept?" and filter keeps the items where the answer is truthy. A list comprehension [p for p in products if p["in_stock"]] does the same thing without the lambda, and most Python style guides prefer the comprehension.
4. As a small adapter that reshapes an argument. Sometimes a library expects a callback with a specific signature, and you have a function that's close but not quite right. A lambda is a fine glue layer.
The adapter lambda is short, has an obvious purpose, and lives right next to the code that uses it.
The thread running through all four cases is the same: the function is one expression, used once, and a name above the call site would add nothing. The moment any of those stops being true, prefer def.
defThe two ways of defining a function are different forms of the same thing: both produce a function object you can call. The differences are about syntax, expressiveness, and what they signal to a reader.
Both versions of square behave identically. Same call signature, same return, same type(square). So why have two forms at all? Because def is a statement that always binds a name, while lambda is an expression you can use inline. That difference is small but real.
| Feature | lambda | def |
|---|---|---|
| Form | Expression | Statement |
| Body | Single expression | Any number of statements |
| Has a name? | No (it's anonymous) | Yes (the name after def) |
| Can be used inline as an argument? | Yes | No (you'd have to define above and pass the name) |
Supports return? | No (the expression is the return) | Yes |
| Supports type annotations? | No | Yes |
| Supports a docstring? | No | Yes |
| Shows up usefully in tracebacks? | As <lambda> | As the function's actual name |
The traceback line is worth a moment. Compare what you see when a lambda raises an error versus a named function:
The frame for the lambda is just <lambda>. You don't get the variable name broken, because the lambda itself doesn't know what name (if any) it was assigned to. With a named function, the traceback would say in broken, which is one less mental hop when debugging.
The diagram shows the two construction paths converging on the same kind of object. The lambda path is a one-line expression that produces a function; the def path is a statement that also produces a function and binds it to a name. Once built, the two are indistinguishable apart from the cosmetic differences in the table above.
The practical rule of thumb: if you'd need to write a name above the call site, you're better off with def. If the function is one expression and lives inline as an argument, lambda is fine.
The single most common real use of lambda is as the key argument to sorted, min, max, and list.sort. These four built-ins all share the same pattern: each takes an iterable and an optional key function that maps each element to the value used for comparison.
A list of cart items is a good running example. Each item is a dictionary with a few fields, and you'll want to sort by different fields at different times.
Sort by price, ascending:
The key function is called once per element. Its return value (the price) is what sorted actually compares. The dictionaries are never compared against each other directly; only the prices are.
Sort by rating, descending. Two equivalent ways:
Both produce the same order. reverse=True is clearer when the field is non-numeric (you can't negate a string), so most code uses that form by default.
Sort by name, alphabetically. Strings compare lexicographically by default:
Sort by a tuple of fields for tie-breaking. Tuples compare element by element, so a key that returns (category, -price) sorts primarily by category, then by price descending within each category:
Cables come first (lexicographically before electronics), and within each category the most expensive item comes first because the price is negated. This "tuple of keys" trick is the standard way to combine multiple sort criteria in one pass.
min and max use the same key argument:
The lambda answers "what value should we compare?", and max/min finds the element whose key is greatest or smallest. Without the key, both would try to compare the dictionaries directly, which doesn't make sense (and raises TypeError in this case).
The flow on the left shows three cart items going into sorted. The orange box is the lambda that pulls the price out of each item. The teal box is sorted doing its comparison work using the key. The green box on the right is the result, ordered by the prices the lambda returned.
Cost: The key function is called once per element, not on every comparison. For a list of N items, sorted calls the key N times and caches the results. So a lambda that does a little work per element is fine; one that does heavy work per element will be felt only N times, not N log N.
list.sort works the same way as sorted, except it sorts the list in place and returns None:
The choice between sorted and list.sort is the same one you'd make without a key: use sorted when you want a new list, use list.sort when you want to mutate the original.
*args, **kwargs, and DefaultsLambdas accept the same parameter syntax as def: positional parameters, keyword parameters, defaults, *args, and **kwargs. The only thing they can't do is have multi-line bodies. The parameter list itself is just as flexible.
A lambda with a default argument:
The rate=0.10 default works exactly like it does in a regular function. If the caller omits rate, the default is used. Keyword arguments also work normally, as the last call shows.
A lambda with *args:
*prices collects any number of positional arguments into a tuple, just as in a normal function. sum(prices) then adds them up. (Calling total() with no arguments returns 0 because sum of an empty tuple is 0.)
A lambda with **kwargs:
The body uses a generator expression inside join, which is still a single expression and so still allowed. **fields collects keyword arguments into a dict, same as in def.
You can combine all the forms:
The body is a single dict literal, which is an expression, so the lambda is still well-formed. That said, a lambda this complex is a strong signal you should be using def instead. The "can I read this in two seconds" test fails here.
There's a real, well-known gotcha with default arguments in lambdas: they're a useful workaround for the late binding behavior that closures inherit. Lambdas defined inside a loop close over variables, not their current values, so a list of lambdas built in a for loop will all see the loop variable's final value:
All three lambdas refer to the same variable n, which by the time they're called has its final value of 3. The standard fix is to capture the value as a default argument, which is bound at lambda-creation time:
value=n evaluates n immediately when the lambda is created, so each lambda has its own captured value. This is one of the few cases where the default-argument syntax is doing real work inside a lambda rather than just being a parameter option.
Cost: This default-binding trick is purely about closure semantics, not performance. It works the same in def-defined functions; the syntactic compactness is just more visible with lambdas.
Lambdas are a small tool. Most uses outside the "callback to a higher-order function" pattern are anti-patterns. Here are the three that come up most often.
Anti-pattern 1: Assigning a lambda to a name.
This is the one PEP 8 calls out explicitly. If you write:
You're using a lambda's anonymity for something that isn't anonymous. The function ends up bound to the name add_one, which is exactly what def is for, and def does it better: the function knows its own name (for tracebacks), can have a docstring, and reads more clearly.
PEP 8's recommendation is direct: use def for any function you're going to give a name. The rewritten version is barely longer and strictly better:
The case where add_one = lambda n: n + 1 is "fine" is honestly rare. The most common reason people reach for it is "I want to write the function on one line". def does that too, just with return:
Some style guides accept that one-line def form for very short helpers. None of them accept the named-lambda form.
Anti-pattern 2: Reaching for `map`/`filter` + lambda when a comprehension would do.
map and filter with lambdas are a heavily Lisp-influenced way to transform a list, and they predate comprehensions. Modern Python style prefers comprehensions for "transform and/or filter a list" because they read top to bottom in roughly the order you'd describe them out loud.
Same result, but the comprehension says "for each price, multiply by 1.08", which is closer to how you'd explain it to a human. Reach for map/filter only when you already have a named function to hand off; if you're building a lambda just to pass to map, you're paying syntax for no real win.
Anti-pattern 3: A multi-clause conditional or nested lambda just to stay on one line.
We saw this one earlier with the classify function. Once a lambda has more than one operator, more than one level of if/else, or a body that's getting hard to scan, switch to def. The "one line" benefit is gone the moment a reader has to puzzle over the line.
The def form takes three lines instead of one, but each line says exactly one thing. That's worth the extra height.
A short summary of when to choose which:
| Situation | Use |
|---|---|
Function used once, inline, as a key, callback, or filter | lambda |
| Function has a name | def |
| Function has more than one operator or branch | def |
| Function needs a docstring, annotations, or multiple statements | def |
| Function should show its name in tracebacks | def |
| Lambda inside a loop that needs to capture the current loop value | lambda value=loop_var: ... |
The honest one-line summary is: `lambda` for tiny anonymous helpers, `def` for everything else.
10 quizzes