Lambda Functions (map, filter, sorted, anonymous functions) - Python Tutorial for Beginners #13
Video: Lambda Functions (map, filter, sorted, anonymous functions) - Python Tutorial for Beginners #13 by Taught by Celeste AI - AI Coding Coach
Python Lambda Functions: Anonymous One-Liners
lambda args: expression. A function with no name. Useful when you need a small function as an argument —sorted(..., key=lambda x: ...),map,filter. Limited to a single expression.
A lambda is just a tiny function defined inline. Use it when defining a full def would be more noise than signal.
The syntax
square = lambda x: x ** 2
print(square(5)) # 25
print(square(8)) # 64
lambda parameters: expression. The result of the expression is the return value. No return keyword.
The above is exactly equivalent to:
def square(x):
return x ** 2
But less ceremony — useful when the function is tiny and only used in one place.
Multiple parameters
multiply = lambda x, y: x * y
print(multiply(3, 4)) # 12
print(multiply(7, 6)) # 42
Comma-separate. You can also use defaults and *args/**kwargs:
greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Bob", "Hi") # "Hi, Bob!"
What lambdas CAN'T do
A lambda body is a single expression — no statements.
- ❌ No
if/elif/elseblocks (but ternarya if cond else bworks). - ❌ No
for/whileloops. - ❌ No assignments (no
x = ...; walrus:=works in Python 3.8+). - ❌ No multiple statements.
- ❌ No
return(the expression is implicitly returned).
If you need any of those, use def.
# OK — ternary expression
classify = lambda n: "even" if n % 2 == 0 else "odd"
# Not OK — needs def
def parse_int(s):
try:
return int(s)
except ValueError:
return None
Where lambdas shine: as arguments
The whole point of lambda is to pass a tiny function to something that takes a function. Three big customers: sorted, map, filter.
sorted with key=
students = ["alice", "Bob", "charlie", "Diana"]
sorted_case_insensitive = sorted(students, key=lambda s: s.lower())
# ['alice', 'Bob', 'charlie', 'Diana']
key= accepts a function that turns each element into a sort key. Lambda is perfect here — the function is one line, used once.
people = [("Alice", 25), ("Bob", 30), ("Charlie", 20)]
by_age = sorted(people, key=lambda p: p[1])
# [('Charlie', 20), ('Alice', 25), ('Bob', 30)]
For sorting by tuple element 1 (age), lambda is shorter than defining a named function.
For common patterns, operator.itemgetter is even cleaner:
from operator import itemgetter
by_age = sorted(people, key=itemgetter(1))
map
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
# [1, 4, 9, 16, 25]
map(func, iterable) applies func to each item, returning a lazy iterator. list(...) to materialize.
But — list comprehensions are usually clearer:
squared = [x ** 2 for x in numbers] # most Pythonic
map shines only when the function already exists:
prices = ["12.50", "8.99", "5.25"]
floats = list(map(float, prices)) # no lambda needed
filter
scores = [45, 78, 92, 33, 88, 61, 95]
passing = list(filter(lambda x: x >= 60, scores))
# [78, 92, 88, 61, 95]
filter(func, iterable) keeps items where func(item) is truthy. Again, comprehensions are usually better:
passing = [x for x in scores if x >= 60]
The Pythonic rule: prefer comprehensions over map/filter + lambda. Use map/filter only when the function is a built-in or pre-defined name.
Lambdas in a list (function tables)
operations = [
lambda x: x + 10,
lambda x: x * 2,
lambda x: x ** 2
]
num = 5
for op in operations:
print(op(num)) # 15, 10, 25
A list of functions is sometimes useful for dispatch tables — though a dict of name→function is usually more readable.
A grade processor
students = [
{"name": "Alice", "scores": [85, 92, 78, 90]},
{"name": "Bob", "scores": [72, 68, 75, 70]},
{"name": "Charlie", "scores": [95, 98, 93, 96]},
]
# Add average to each student
with_avg = list(map(
lambda s: {**s, "average": sum(s["scores"]) / len(s["scores"])},
students
))
# Honors: average >= 80
honors = list(filter(lambda s: s["average"] >= 80, with_avg))
# Rank by average, descending
ranked = sorted(with_avg, key=lambda s: s["average"], reverse=True)
Three lambdas, three different built-ins. Each lambda is a one-liner — a def would be heavier.
Closures: lambda captures variables
def multiplier(n):
return lambda x: x * n
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
The inner lambda captures n from the enclosing scope. This is a closure — the lambda remembers its environment. Useful for parameterized callbacks.
The late-binding gotcha
funcs = []
for i in range(3):
funcs.append(lambda: i)
for f in funcs:
print(f()) # 2 2 2 — not 0 1 2!
All three lambdas refer to the same i, which is 2 after the loop. Closures bind by name, not by value.
The fix: use a default argument to capture the current value:
funcs = []
for i in range(3):
funcs.append(lambda i=i: i) # default captures current i
for f in funcs:
print(f()) # 0 1 2
This trips up almost everyone the first time.
When to skip lambda
For anything beyond a single expression — use def. Specifically:
- More than ~40 chars →
defis more readable. - Used more than once → name it with
def. - Has a non-obvious purpose → name + docstring.
- Has side effects →
defmakes the intent clearer.
# Too clever
sorted(items, key=lambda x: (-x['priority'], x['name'].lower(), x['date']))
# Better
def sort_key(item):
return (-item['priority'], item['name'].lower(), item['date'])
sorted(items, key=sort_key)
Naming the function helps the next reader.
Lambda is just a function
square = lambda x: x ** 2
print(type(square)) # <class 'function'>
print(square.__name__) # '<lambda>'
It's a regular function object. The only difference is the lack of a name (__name__ is '<lambda>') and the body restriction.
Built-in alternatives
Many lambdas duplicate something in operator:
from operator import add, mul, itemgetter, attrgetter
# instead of: lambda a, b: a + b
sum(numbers) # for the common case
# or: reduce(add, numbers)
# instead of: lambda x: x[1]
key=itemgetter(1)
# instead of: lambda x: x.name
key=attrgetter("name")
itemgetter and attrgetter are slightly faster and more readable than lambda for the same job.
Common stumbles
Multi-line lambda. Doesn't exist. Use def.
Lambda with print. print returns None, so lambda x: print(x) is a function that returns None. Usually you want def f(x): print(x) or just print directly.
Late binding in loops. Use lambda x=x: ... to capture the current value.
Naming a lambda. square = lambda x: x ** 2 — pylint will warn, "use def instead." It's correct: if you're naming it, just write def.
Lambdas as default arguments. def f(callback=lambda: None) — works, but the same lambda is shared across all calls (it's defined once). Usually fine for a no-op, but be aware.
What's next
Lesson 14: list comprehensions, dict comprehensions, generator expressions. Compact, Pythonic alternatives to map/filter/loop.
Recap
lambda args: expr — anonymous single-expression function. Use as argument to sorted (key=), map, filter, or anywhere a small inline function is more readable than a def. Single expression only — no statements, no loops. For anything more, use def. Watch out for late-binding closures in loops; use lambda x=x: to capture. Prefer list comprehensions over map/filter + lambda for transformations.
Next lesson: comprehensions and generator expressions.