Skip to content

Map, Filter and Reduce#

map, filter, and functools.reduce are the classic functional pipeline tools in Python: transform every element, keep those matching a predicate, and fold a sequence into one value. Python 3 returns lazy iterators from map and filter — understanding laziness is essential for performance and correctness.

How to use this page

Read with Higher Order Functions and Lambda Functions. For lazy traversal protocol details, see Custom Iterators. Modern style often prefers comprehensions — know both.

At a glance
Track Python Advanced → Functional Programming
Sections 12 major topics
Outline Use the right-hand TOC to jump

Topics: The trio at a glance · map in depth · filter in depth · functools.reduce · Laziness and single consumption · Building pipelines · With named functions (cleaner than lambdas) · operator module — avoid trivial lambdas · … (+4 more)

  1. The trio at a glance
  2. map in depth
  3. filter in depth
  4. functools.reduce
  5. Laziness and single consumption
  6. Building pipelines
  7. With named functions (cleaner than lambdas)
  8. operator module — avoid trivial lambdas
  9. Side effects in map — anti-pattern
  10. Performance notes (Python 3.10+)
  11. Typing
  12. Real-world uses

The trio at a glance#

Function Purpose Python 3 return type
map(func, iterable) Transform each element Iterator
filter(pred, iterable) Keep elements where pred(x) is truthy Iterator
reduce(func, iterable) Accumulate to single value Scalar (any type)
nums = [1, 2, 3, 4, 5]

squares = map(lambda x: x ** 2, nums)
evens = filter(lambda x: x % 2 == 0, nums)

list(squares)  # [1, 4, 9, 16, 25]
list(evens)    # [2, 4]

map in depth#

def to_str(x: int) -> str:
    return str(x)

list(map(to_str, [1, 2, 3]))           # ['1', '2', '3']

# multiple iterables — stops at shortest
list(map(lambda a, b: a + b, [1, 2, 3], [10, 20, 30]))
# [11, 22, 33]

list(map(lambda a, b: a + b, [1, 2], [10, 20, 30]))
# [11, 22] — truncated

map vs list comprehension#

# map
upper = map(str.upper, ["a", "b"])

# comprehension — usually more readable
upper = (s.upper() for s in ["a", "b"])   # lazy
upper = [s.upper() for s in ["a", "b"]]   # eager
Style Prefer when
Comprehension Pythonic transformation, conditionals
map Function already named, functional chains, passing callable as arg

Builtin map with None

map(None, a, b) pairs elements like zip(a, b) in Python 3 — rare; prefer zip.


filter in depth#

def is_positive(x: int) -> bool:
    return x > 0

list(filter(is_positive, [-1, 0, 2, 3]))  # [2, 3]

# pred=None keeps truthy values
list(filter(None, [0, "", "hi", [], [1]]))  # ['hi', [1]]

filter vs comprehension#

evens = filter(lambda x: x % 2 == 0, nums)
evens = [x for x in nums if x % 2 == 0]

Use filter when the predicate is a named function passed around; use comprehension for inline conditions.


functools.reduce#

from functools import reduce

reduce(lambda acc, x: acc + x, [1, 2, 3, 4])  # 10

# with initial accumulator
reduce(lambda acc, x: acc + x, [1, 2, 3], 100)  # 106

# empty iterable — needs initial value
reduce(lambda acc, x: acc + x, [], 0)  # 0
# reduce(lambda acc, x: acc + x, [])  # TypeError
Case Behavior
Non-empty, no init First element becomes initial acc
Empty, no init TypeError
Empty, with init Returns init

Interview trap — empty reduce

Always provide an initial value when the iterable may be empty and you need a defined result.


Laziness and single consumption#

it = map(lambda x: x * 2, range(3))
next(it)  # 0
next(it)  # 2
list(it)  # [4] — remainder only

Chained lazy iterators defer work until consumption — good for memory, tricky when reused.

pipeline = map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, range(10)))
sum(pipeline)  # 120 — computed on the fly

Building pipelines#

Classic functional chain#

from functools import reduce

nums = range(1, 11)

result = reduce(
    lambda acc, x: acc + x,
    map(
        lambda x: x ** 2,
        filter(lambda x: x % 2 == 0, nums),
    ),
)
# 220 — sum of squares of evens 2..10

Comprehension equivalent (often clearer)#

result = sum(x ** 2 for x in nums if x % 2 == 0)

itertools alternatives#

import itertools

result = sum(
    x ** 2
    for x in itertools.filterfalse(lambda x: x % 2, nums)  # odd filter inverse
)

With named functions (cleaner than lambdas)#

def square(x: int) -> int:
    return x * x

def is_even(x: int) -> bool:
    return x % 2 == 0

def add(acc: int, x: int) -> int:
    return acc + x

from functools import reduce, partial

sum_even_squares = lambda data: reduce(
    add,
    map(square, filter(is_even, data)),
)

Partial Functions fix common arguments in pipelines.


operator module — avoid trivial lambdas#

from functools import reduce
import operator

reduce(operator.add, [1, 2, 3, 4])       # 10
list(map(operator.neg, [1, -2, 3]))      # [-1, 2, -3]

# itemgetter for sorting keys — related pattern
from operator import itemgetter
sorted(users, key=itemgetter("name"))

Side effects in map — anti-pattern#

# BAD — map for side effects
list(map(print, items))  # works but idiomatically wrong

# GOOD
for item in items:
    print(item)

map is for transformation, not imperative loops. Linters flag map(print, ...).


Performance notes (Python 3.10+)#

Pattern Relative speed Notes
List comprehension Fastest for eager lists C-optimized
map / filter Similar, iterator overhead Better if not fully consumed
reduce in Python Slower than sum, math.prod Use builtins when available
Generator pipeline Best memory Worst if you need random access
# prefer builtin aggregations
sum(x ** 2 for x in nums if x % 2 == 0)
math.prod(nums)
max(nums)
any(pred(x) for x in nums)

Typing#

from typing import Iterator
from functools import reduce

def pipeline(data: list[int]) -> int:
    squared: Iterator[int] = map(lambda x: x ** 2, data)
    return reduce(int.__add__, squared, 0)

Modern code often uses from collections.abc import Iterable and explicit type vars in higher-order helpers.


Real-world uses#

Domain Pattern
ETL map normalize fields → filter invalid rows → reduce merge
Config filter(None, ...) strip falsy entries
Parsing map(int, line.split())
DSA reduce for associative folds (with care)

Interview traps (quick reference)#

Trap What goes wrong Safe approach
map result is a list in Py3 Type confusion list(map(...)) if you need list
Empty reduce without init TypeError Provide initializer
Reusing spent iterator Missing elements Materialize or recreate
filter forgot truthiness Keeps wrong items Remember None means "truthy"
map for side effects Unpythonic, hard to read for loop

Mental model checklist#

  1. What return type do map and filter produce in Python 3?
  2. When must reduce have an initial accumulator?
  3. How do you express map + filter + sum as one comprehension?
  4. Why is map(print, xs) discouraged?
  5. When is reduce still the right tool over sum / any / all?

What's next#

Topic Page
Higher-order functions Higher Order Functions
Lambda Lambda Functions
Partial Partial Functions
Custom iterators Custom Iterators
Closures in HOFs Closures