Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. Python lambda functions: when one line is enough
Learn

Python lambda functions: when one line is enough

A Python lambda is a one-expression function with no name. Where it beats def, why lambdas made in a loop all return the last value, and what PEP 8 says.

By Max Arthur
Co-Founder & Content Marketer·October 21, 2026·11 Min read
White text reading 'Lambda functions in Python' on a dark purple dotted background

A Python lambda is a small anonymous function defined with the lambda keyword, in the form lambda arguments: expression. It can take any number of arguments but holds exactly one expression, whose value it returns automatically. Lambdas are most useful as short, throwaway functions passed to another function, such as the key= argument of sorted().

Here is the classic case, sorting a list of dictionaries by one field:

people = [
    {"name": "Ada", "age": 36},
    {"name": "Linus", "age": 28},
    {"name": "Grace", "age": 45},
]

youngest_first = sorted(people, key=lambda p: p["age"])
print([p["name"] for p in youngest_first])   # ['Linus', 'Ada', 'Grace']

lambda p: p["age"] is a function that takes one dictionary and returns its age. sorted() calls it once per item and sorts by whatever comes back. Writing a separate named function for a job that small would be more ceremony than the job deserves.

That is where lambdas shine. This post also covers where they don't: what a lambda cannot contain, the loop bug that catches nearly everyone once, and the style rule against giving a lambda a name. Every output was measured in Pyodide running Python 3.13.2, the same runtime Devpuff's Python lessons use.

Lambda syntax

A lambda is the keyword, the parameters, a colon, and one expression. There is no return; the value of the expression is what comes back:

square = lambda x: x * x
print(square(4))   # 16

(Assigning a lambda to a name, as on that first line, is exactly what Python's style guide tells you not to do in real code. It is fine for showing the syntax, and the section near the end explains why.)

Every kind of parameter a normal function accepts works in a lambda, including defaults, *args and **kwargs:

print((lambda x, y=2: x ** y)(5))           # 25
print((lambda *a: sum(a))(1, 2, 3))         # 6
print((lambda **k: sorted(k))(b=1, a=2))    # ['a', 'b']

Wrapping the lambda in parentheses and calling it straight away works, though you will rarely see it outside examples like these. A lambda with no parameters is written lambda: ... with nothing before the colon.

For branching, use a conditional expression, because an if statement is not allowed:

parity = lambda n: "even" if n % 2 == 0 else "odd"
print(parity(3))   # odd

Lambda vs def: what actually differs

Less than most tutorials suggest. A lambda is not a special kind of function; it is a different way of writing an ordinary one:

square = lambda x: x * x

def square_def(x):
    return x * x

print(type(square) is type(square_def))   # True
print(square.__name__)                    # <lambda>
print(square_def.__name__)                # square_def

Both have the type function. Comparing the two with the dis module, which shows the instructions Python compiles a function into, gave identical bytecode. Timed over 200,000 calls each, the lambda took 1.02 times as long as the def, which is measurement noise. A lambda is not faster, and it is not slower.

That leaves two real differences:

  1. A lambda holds one expression. No statements, no second line.
  2. A lambda has no name. Its __name__ is the generic <lambda>.

The second one matters more than it sounds, because the name is what shows up when something goes wrong:

items = [4, 2, 0]
print(list(map(lambda x: 10 / x, items)))
Traceback (most recent call last):
  File "<exec>", line 2, in <module>
  File "<exec>", line 2, in <lambda>
ZeroDivisionError: division by zero

(Pyodide labels the file <exec>; a script on disk shows its file name and line number.) The traceback tells you a lambda failed, but not which one. With three lambdas on one line, or the same lambda pattern repeated across a module, that is a slower hunt than a traceback naming safe_ratio would be.

Where lambdas earn their place: key= in sorted, max and min

The best use of a lambda is the key= argument. sorted(), list.sort(), max() and min() all accept one, and it is almost always a tiny function used exactly once:

oldest = max(people, key=lambda p: p["age"])
print(oldest["name"])   # Grace

To sort by more than one thing, return a tuple. Python compares tuples item by item, so the second value only breaks ties in the first:

pairs = [("b", 2), ("a", 2), ("c", 1)]

print(sorted(pairs, key=lambda t: (t[1], t[0])))
# [('c', 1), ('a', 2), ('b', 2)]

print(sorted(pairs, key=lambda t: (-t[1], t[0])))
# [('a', 2), ('b', 2), ('c', 1)]

The minus sign in the second call sorts the numbers from high to low while the letters stay A to Z, which reverse=True cannot do because it reverses everything. The trick only works for numbers; you cannot negate a string. The full set of sorting options, including stability and reverse, is in how to sort a list in Python.

Sort a dictionary by value

The same pattern sorts a dictionary by its values, which is one of the most searched lambda questions and a staple of looping through a dictionary. items() gives (key, value) pairs, and the lambda picks the value:

scores = {"Ada": 91, "Linus": 78, "Grace": 95}

print(sorted(scores.items(), key=lambda kv: kv[1], reverse=True))
# [('Grace', 95), ('Ada', 91), ('Linus', 78)]

If you only need the keys, scores.get is already a function from key to value, so no lambda is needed:

print(sorted(scores, key=scores.get))   # ['Linus', 'Ada', 'Grace']
print(max(scores, key=scores.get))      # Grace

Often you do not need a lambda at all

If the function you want already exists, pass it directly:

words = ["banana", "apple", "Cherry"]

print(sorted(words))                  # ['Cherry', 'apple', 'banana']
print(sorted(words, key=str.lower))   # ['apple', 'banana', 'Cherry']

key=lambda w: w.lower() gives the same result, but it is a lambda whose only job is to call a function that was already there. key=len, key=abs and key=str.lower all read better without the wrapper. For pulling out a field, the operator module has ready-made versions: key=operator.itemgetter("age") sorts people exactly like the lambda at the top of this post.

map and filter vs comprehensions

map() and filter() are the other common home for lambdas:

nums = [1, 2, 3, 4, 5, 6]

print(list(map(lambda n: n * 2, nums)))           # [2, 4, 6, 8, 10, 12]
print(list(filter(lambda n: n % 2 == 0, nums)))   # [2, 4, 6]

The same two results as comprehensions:

print([n * 2 for n in nums])              # [2, 4, 6, 8, 10, 12]
print([n for n in nums if n % 2 == 0])    # [2, 4, 6]

The comprehensions are shorter, need no lambda, and produce a list directly instead of an object you have to wrap in list(). Most Python code uses them for this. map still reads well when the function already has a name, as in map(int, line.split()), which turns a line of text into numbers; split() and join() in Python uses exactly that pattern.

A map object can only be used once

doubled = map(lambda n: n * 2, nums)

print(list(doubled))   # [2, 4, 6, 8, 10, 12]
print(list(doubled))   # []

map() and filter() return lazy iterators, not lists. They produce each value once, on demand, and once used up they stay empty, with no error to warn you. zip() behaves the same way. If you need the results twice, convert to a list once and keep the list.

functools.reduce() takes a lambda as well, as in reduce(lambda a, b: a + b, nums), which gives 21. For sums, sum(nums) says the same thing in a word, and most reductions have a built-in like it.

Lambdas as default factories and returned functions

Some functions take another function as a factory, calling it whenever they need a fresh value. collections.defaultdict is the common one:

from collections import defaultdict

labels = defaultdict(lambda: "unknown")
labels["x"] = "known"
print(labels["x"], labels["y"])   # known unknown
print(dict(labels))               # {'x': 'known', 'y': 'unknown'}

Note the second line of output: looking up the missing key "y" did not just return the default, it stored it. A defaultdict grows every time you read a key that is not there, which surprises people who only meant to check.

When the default is zero or an empty list, pass the type instead of a lambda. defaultdict(int) and defaultdict(list) work because calling int() gives 0 and calling list() gives [].

A lambda can also be the return value of another function, which is a compact way to build closures:

def make_multiplier(n):
    return lambda x: x * n

double = make_multiplier(2)
print(double(5))           # 10
print(double.__name__)     # <lambda>

The lambda remembers n from the call that created it. The trade-off from earlier still applies, though: double has a perfectly good name in your code, and its __name__ is still <lambda>.

What a lambda can't contain

The body of a lambda must be a single expression. Statements are syntax errors:

lambda x: return x   # SyntaxError: invalid syntax
lambda x: (y = x)    # SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
lambda x: assert x   # SyntaxError: invalid syntax

Without the parentheses, lambda x: y = x gives a stranger message, cannot assign to lambda, because Python reads the whole line as an attempt to assign x to the lambda itself.

That rules out return, assignment, assert, raise, pass, if statements, for and while loops, and try/except. Expressions are all fine, which still leaves a lot: conditional expressions, comprehensions, function calls and arithmetic.

Two edge cases are worth knowing. The walrus operator := that the error message suggests is an expression, so it is allowed:

f = lambda x: (y := x * 2) + y
print(f(3))   # 12

And calling a function for its side effect works, because a call is an expression. lambda: print("hi") prints when called and returns None, since that is what print returns.

If you find yourself reaching for the walrus or chaining conditional expressions to fit logic into one line, that is the sign to write a def.

The loop trap: why every lambda returns the last value

This is the lambda bug almost everyone meets once:

callbacks = [lambda: i for i in range(3)]
print([f() for f in callbacks])   # [2, 2, 2]

You might expect [0, 1, 2]. Each lambda refers to the variable i, not to the value i held when the lambda was created. Python looks the variable up when the function is called, and by then the loop has finished and i is 2. This is called late binding, and the Python docs have a FAQ entry about this exact case.

It is not really about lambdas. A def inside the loop does the same thing, because any function that uses a variable from its surrounding scope captures the variable rather than its value. JavaScript had the identical problem with var in loops, which is covered in JavaScript closures explained.

The usual fix is a default argument, because defaults are evaluated once, when the function is created:

callbacks = [lambda i=i: i for i in range(3)]
print([f() for f in callbacks])   # [0, 1, 2]

i=i reads oddly, but it means "make a parameter called i whose default is the current value of i". The catch is that a caller can now pass i and override it. functools.partial avoids that by binding the value without adding a parameter:

from functools import partial
from operator import mul

multipliers = [partial(mul, i) for i in range(3)]
print([f(10) for f in multipliers])   # [0, 10, 20]

Where this bites in real code: building GUI button handlers in a loop, filling a dictionary of callbacks, and registering event listeners. In every case each handler ends up acting on the last item.

Don't assign a lambda to a name

PEP 8, Python's style guide, is direct about this: "Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier." Linters such as flake8 and Ruff report it as rule E731.

# Flagged:
square = lambda x: x * x

# Preferred:
def square(x):
    return x * x

The reason is the name. The def version's __name__ is square, so tracebacks, profilers and logs say square. The lambda version says <lambda>, and it saves you nothing, since you typed a name either way. The def also leaves room for a docstring and a second line when the function inevitably grows.

A dictionary of small operations is a common, readable exception:

ops = {"add": lambda a, b: a + b, "sub": lambda a, b: a - b}
print(ops["sub"](10, 3))   # 7

Even there, operator.add and operator.sub already exist and would carry real names into a traceback.

Lambda or def: a quick decision

  • A small function passed to another function and used once (a sort key, a callback): lambda.
  • The function already exists (len, str.lower, int, anything in operator): pass it directly, no lambda.
  • Transforming or filtering a list: a comprehension.
  • It needs a name, a docstring, more than one line, error handling or its own tests: def.

Quick reference

lambda x: x * 2                         # one parameter
lambda a, b=1: a + b                    # defaults work
lambda: "no args"                       # no parameters
lambda n: "even" if n % 2 == 0 else "odd"   # branching
sorted(items, key=lambda p: p["age"])   # the classic use
sorted(items, key=lambda t: (-t[1], t[0]))  # multi-key, mixed direction
sorted(words, key=str.lower)            # no lambda needed
[lambda i=i: i for i in range(3)]       # capture the loop value

Want to write functions in lessons where the Python runs as you type? Start the Python track.

Keep reading

More from the blog

White text reading 'What if name equals main does in Python' on a dark purple dotted background
October 27, 2026·8 min readLearn

What if __name__ == '__main__' actually does in Python

In Python, if __name__ == '__main__' runs code only when you run the file directly, not when it is imported. What __name__ holds and when you need it.

Read more
White text reading 'How to reverse a list in Python' on a dark purple dotted background
October 26, 2026·8 min readLearn

How to reverse a list in Python (and a string)

To reverse a list in Python, use reverse() to change it in place, [::-1] for a reversed copy, or reversed() to loop backwards. Which to use and why.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons