Python Lambda Expressions

Learn Python lambda (anonymous) functions: syntax, map(), filter(), sorted(), reduce(), and when to use def instead.

Beginner 8 min read 8 examples

Lambda Basics

A lambda is a one-line anonymous function. Syntax: lambda parameters: expression. The expression is automatically returned.

Python
# Lambda vs equivalent def
double_lambda = lambda x: x * 2

def double_def(x):
    return x * 2

print(double_lambda(5))     # 10
print(double_def(5))        # 10

# Multiple parameters
add = lambda a, b: a + b
print(add(3, 4))    # 7

# Default parameters
greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
print(greet("Alice"))           # Hello, Alice!
print(greet("Bob", "Hi"))       # Hi, Bob!

# No parameters
pi = lambda: 3.14159
print(pi())     # 3.14159

# Lambda is an expression - can be used inline
result = (lambda x, y: x + y)(10, 20)
print(result)   # 30

# Lambda with conditional expression
classify = lambda n: "positive" if n > 0 else "negative" if n < 0 else "zero"
print(classify(5))    # positive
print(classify(-3))   # negative
print(classify(0))    # zero

# Don't do this (PEP 8 says don't assign lambda to a variable)
# f = lambda x: x + 1   # use def instead:
def f(x):
    return x + 1

map() and filter()

map() applies a function to every item. filter() keeps items where the function returns truthy. Both return lazy iterators.

Python
# --- map(function, iterable) ---
numbers = [1, 2, 3, 4, 5]

# Lambda with map
squared = list(map(lambda x: x**2, numbers))
print(squared)  # [1, 4, 9, 16, 25]

# Built-in function with map (faster than lambda)
strings = ["1", "2", "3", "4"]
ints = list(map(int, strings))
print(ints)     # [1, 2, 3, 4]

words = ["hello", "world", "python"]
upper = list(map(str.upper, words))
print(upper)    # ['HELLO', 'WORLD', 'PYTHON']

# map with multiple iterables
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
print(sums)     # [11, 22, 33]

# Equivalent list comprehension (usually preferred)
squared_comp = [x**2 for x in numbers]
print(squared_comp)     # [1, 4, 9, 16, 25]

# --- filter(function, iterable) ---
numbers = range(-5, 6)

positives = list(filter(lambda x: x > 0, numbers))
print(positives)    # [1, 2, 3, 4, 5]

# filter(None, iterable) removes falsy values
mixed = [0, 1, "", "hello", None, [], [1, 2], False, True]
truthy = list(filter(None, mixed))
print(truthy)   # [1, 'hello', [1, 2], True]

# Equivalent comprehension
positives_comp = [x for x in range(-5, 6) if x > 0]
print(positives_comp)

# Chaining map and filter
data = ["  alice  ", "", "  BOB  ", "   ", "charlie"]
cleaned = list(filter(None, map(str.strip, data)))
print(cleaned)  # ['alice', 'BOB', 'charlie']

# Same with comprehension (arguably clearer)
cleaned2 = [s.strip() for s in data if s.strip()]
print(cleaned2)

sorted() with key=

The key= argument is the most common use of lambda in Python. It specifies a function to extract the sort key from each item.

Python
import operator

# Sort strings by length
words = ["banana", "apple", "cherry", "fig", "date"]
by_length = sorted(words, key=lambda w: len(w))
print(by_length)    # ['fig', 'date', 'apple', 'banana', 'cherry']

# Sort strings case-insensitively
words2 = ["Banana", "apple", "Cherry", "DATE"]
by_lower = sorted(words2, key=lambda w: w.lower())
print(by_lower)     # ['apple', 'Banana', 'Cherry', 'DATE']
# Better: key=str.casefold

# Sort by last name
names = ["Alice Smith", "Charlie Brown", "Bob Jones"]
by_last = sorted(names, key=lambda n: n.split()[-1])
print(by_last)  # ['Charlie Brown', 'Bob Jones', 'Alice Smith']

# Sort list of dicts
people = [
    {"name": "Charlie", "age": 35, "score": 88},
    {"name": "Alice",   "age": 25, "score": 95},
    {"name": "Bob",     "age": 30, "score": 72},
]

by_age   = sorted(people, key=lambda p: p["age"])
by_score = sorted(people, key=lambda p: p["score"], reverse=True)

# operator.itemgetter is faster and more readable for simple lookups
by_age2  = sorted(people, key=operator.itemgetter("age"))
print([p["name"] for p in by_age2])    # ['Alice', 'Bob', 'Charlie']

# Sort by multiple fields: primary by age, secondary by score
by_multi = sorted(people, key=lambda p: (p["age"], -p["score"]))

# Sort objects by attribute
from operator import attrgetter

class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa  = gpa
    def __repr__(self):
        return f"Student({self.name}, {self.gpa})"

students = [Student("Alice", 3.9), Student("Bob", 3.5), Student("Charlie", 3.7)]
by_gpa = sorted(students, key=attrgetter("gpa"), reverse=True)
print(by_gpa)   # [Student(Alice, 3.9), Student(Charlie, 3.7), Student(Bob, 3.5)]

functools.reduce()

reduce() applies a function cumulatively to a sequence, reducing it to a single value.

Python
from functools import reduce
import operator

numbers = [1, 2, 3, 4, 5]

# Sum using reduce (prefer sum() in practice)
total = reduce(lambda acc, x: acc + x, numbers)
print(total)    # 15

# Product (no built-in)
product = reduce(lambda acc, x: acc * x, numbers)
print(product)  # 120

# Same using operator module
product2 = reduce(operator.mul, numbers)
print(product2)     # 120

# With initial value
total_from_100 = reduce(lambda acc, x: acc + x, numbers, 100)
print(total_from_100)   # 115

# Maximum without max() (just for illustration)
max_val = reduce(lambda a, b: a if a > b else b, numbers)
print(max_val)  # 5

# Deep merge dicts
dicts = [{"a": 1}, {"b": 2}, {"c": 3}]
merged = reduce(lambda a, b: {**a, **b}, dicts)
print(merged)   # {'a': 1, 'b': 2, 'c': 3}

# In practice, prefer built-ins over reduce:
# sum(), max(), min(), any(), all(), " ".join()
print(sum(numbers))         # 15  (cleaner than reduce)
print(max(numbers))         # 5
print(" ".join(["a","b"]))  # a b

Lambda Limitations

Python
# Lambdas can only contain ONE expression
# These are NOT valid in lambda:
# - assignments
# - if/elif/else statements (only ternary expression)
# - loops
# - try/except
# - multiple statements

# WRONG: assignment in lambda
# f = lambda x: y = x + 1  # SyntaxError

# WRONG: for loop in lambda
# f = lambda xs: for x in xs: print(x)  # SyntaxError

# Lambdas cannot have docstrings
def documented(x):
    """This function squares x."""
    return x ** 2

# Lambda has no docstring:
sq = lambda x: x ** 2
print(sq.__doc__)   # None

# Lambda has no name (shows as )
print(sq.__name__)          #   - unhelpful for debugging
print(documented.__name__)  # documented

# Rule: when a lambda becomes complex, convert to def
# BAD: unreadable lambda
process = lambda data: [x**2 for x in data if x > 0 and x % 2 == 0]

# GOOD: readable named function
def process_evens(data):
    """Return squares of positive even numbers."""
    return [x**2 for x in data if x > 0 and x % 2 == 0]

# Summary: use lambda only as a short key/callback function
# For everything else, use def

# Good lambda uses (concise key functions):
data = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
sorted(data, key=lambda d: d["age"])    # clear and concise

# Equivalent with operator (even better for simple attribute/item access):
from operator import itemgetter
sorted(data, key=itemgetter("age"))
Prefer operator.itemgetter over lambda for simple lookups

operator.itemgetter("key") is faster than lambda d: d["key"] and more explicit. Similarly, operator.attrgetter("attr") beats lambda obj: obj.attr. These are micro-optimizations, but they also signal intent more clearly.

Frequently Asked Questions

A lambda is a small anonymous function defined in a single expression: lambda arguments: expression. It is equivalent to a regular function but has no name and can only contain one expression (no statements, no multi-line logic). Lambda functions are most useful as short callback functions passed to sorted(), map(), filter(), and similar higher-order functions.

Use lambda for short, throwaway functions used in one place - especially as the key argument to sorted() or as a callback in map()/filter(). Use def when: the function needs a name for clarity or reuse, the function has more than one expression, you need docstrings, type hints, or multiple statements. PEP 8 specifically says: do not assign a lambda to a variable (f = lambda x: x+1) - use def for named functions.

For most practical code, the difference is negligible. List comprehensions are generally preferred in Python because they are more readable and Pythonic. map() with a lambda is slightly slower than map() with a built-in function (because lambda has call overhead), and roughly equivalent to a list comprehension. Use whichever reads more naturally: comprehensions for transformation/filtering, map() when applying a named function.

The operator module provides functions for common operations that replace simple lambdas. Instead of lambda x: x[1], use operator.itemgetter(1). Instead of lambda x: x.attr, use operator.attrgetter("attr"). These are faster than equivalent lambdas because they avoid Python function call overhead. Example: sorted(data, key=operator.itemgetter("age")).