Part of Python for Beginners

Lambda Functions (map, filter, sorted, anonymous functions) - Python Tutorial for Beginners #13

Sandy LaneSandy Lane

Video: Lambda Functions (map, filter, sorted, anonymous functions) - Python Tutorial for Beginners #13 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

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/else blocks (but ternary a if cond else b works).
  • ❌ No for/while loops.
  • ❌ 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 → def is more readable.
  • Used more than once → name it with def.
  • Has a non-obvious purpose → name + docstring.
  • Has side effects → def makes 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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.