Module 7 · Functional & Decorative Python ⏱ 19 min

map, filter & reduce

By the end of this lesson you will be able to:
  • Transform every item in an iterable with map
  • Keep only matching items with filter
  • Combine an iterable into a single value with reduce
  • Decide when built-ins beat manual loops

Three loop patterns appear so often that Python gives them names. You have a list and you want every item transformed: that is map. You want only the items that pass a test: that is filter. You want to collapse the whole list into one value — a sum, a product, a maximum: that is reduce.

Each of these is a higher-order function. You supply the data and the behaviour; the tool supplies the loop. Used well, they make intent explicit: map(len, words) says get the length of every word more clearly than a four-line loop. Used poorly, they obscure simple logic behind unnecessary abstraction. This lesson teaches you all three, then shows you when to reach for a comprehension or a plain loop instead.

flowchart LR
  I["[1, 2, 3]"] --> M["map(double, items)"]
  M --> O["[2, 4, 6]"]
  style M fill:#3776ab,color:#fff
map applies a function to every item and returns an iterator of results.

map: transform every item

map(function, iterable) calls the function on each element and yields the results, one by one, in order. In Python 3 it returns an iterator, not a list, so if you want to print it or index it you must convert it with list().

map pairs naturally with lambda for small, throwaway transformations, but you can pass any function — built-in, named, or nested. A lambda is just an anonymous function written inline: lambda x: x * 2 takes one argument and returns its double. The syntax is limited to a single expression, which is both a strength (concise) and a weakness (no statements).

map with a built-in, a lambda, and a named function.
words = ['apple', 'pie', 'custard']

# map returns an iterator; list() materialises it
lengths = list(map(len, words))
print(lengths)

squares = list(map(lambda n: n * n, [1, 2, 3, 4]))
print(squares)

def shout(s):
    return s.upper() + '!'
print(list(map(shout, words)))

filter: keep only what passes

filter(function, iterable) keeps the items for which the function returns a truthy value. Like map, it returns an iterator in Python 3. The function should return True or False; anything truthy or falsy works, but returning explicit booleans is clearest.

A common trick is to pass None as the function. filter(None, items) keeps only the truthy items, dropping empty strings, zeros, and None values in one go. This is the functional equivalent of a list-comprehension filter, and it is often faster to read once you recognise the idiom.

flowchart LR
  I["[1, 2, 3, 4]"] --> F["filter(is_even, items)"]
  F --> O["[2, 4]"]
  style F fill:#3776ab,color:#fff
filter drops items that fail the test and yields only the survivors.
filter keeps evens; filter(None, ...) drops falsy values.
nums = [0, 1, 2, 3, 4, 5]

evens = list(filter(lambda n: n % 2 == 0, nums))
print(evens)

mixed = [0, 'hi', '', None, 42, []]
truthy = list(filter(None, mixed))
print(truthy)

reduce: collapse to one value

reduce lives in the functools module, not the built-in namespace. It takes a function of two arguments and an iterable, then walks through the iterable left to right, combining each item with the running result.

reduce(add, [1, 2, 3, 4]) computes ((1 + 2) + 3) + 4, which is 10. The first call uses the first two items; after that, the accumulated result is paired with the next item. You can also supply an initial value that sits before the first item. Providing an initial value is safer because it handles empty iterables gracefully; without one, reduce raises a TypeError on an empty sequence.

flowchart LR
  A["1 + 2 = 3"] --> B["3 + 3 = 6"]
  B --> C["6 + 4 = 10"]
  C --> D["final result"]
  style B fill:#3776ab,color:#fff
reduce walks left to right, folding each item into an accumulator.
reduce for sum, product, and string joining.
from functools import reduce

nums = [1, 2, 3, 4]

total = reduce(lambda a, b: a + b, nums)
print('sum:', total)

product = reduce(lambda a, b: a * b, nums, 1)
print('product:', product)

joined = reduce(lambda a, b: a + '-' + b, ['a', 'b', 'c'])
print('joined:', joined)

When to use what

map and filter are often clearer as comprehensions. [n * n for n in nums] does the same job as map(lambda n: n * n, nums) and is usually more readable, especially when the transformation is simple. reduce has no comprehension equivalent, but many reduces are already built-ins: sum, min, max, all, any, and str.join. Reach for reduce only when the built-in does not exist.

The real value of learning these three is not the syntax — it is recognising the pattern. When you see a loop that transforms every item, think map. When you see a loop that appends conditionally, think filter. When you see a loop that accumulates a single result, think reduce. Naming the pattern is the first step toward replacing it with the right tool.

Performance and readability trade-offs

For small lists, the choice between map, comprehensions, and loops is mostly a matter of taste. For very large datasets, map and filter can be more memory-efficient because they produce values lazily, one at a time, rather than building an entire list in memory. However, if you immediately wrap them in list(), that advantage disappears.

Readability should be your primary guide. Python's philosophy prefers explicit over implicit. If a comprehension makes the intent obvious, use it. If map with a named function makes the intent clearer, use that. If the logic is complex enough to need multiple statements, a plain for loop is the right choice.

Exercise

Use map to return a list of the lengths of every word in words. Do not write a loop.

def word_lengths(words):
    # use map and list
    pass
Exercise

What does this print? Watch for the iterator trap.

nums = [1, 2, 3]
m = map(lambda n: n * 2, nums)
print(list(m))
print(list(m))
Exercise

Use filter to return a list of only the positive numbers from nums. Numbers greater than zero are positive.

def positives(nums):
    # use filter and list
    pass
Exercise

This function tries to return the maximum value using reduce, but it crashes on an empty list. Fix it by providing an appropriate initial value so reduce has something to start with.

from functools import reduce

def max_value(nums):
    return reduce(lambda a, b: a if a > b else b, nums)
Exercise

Use reduce to compute the product of all numbers in nums. Start with an initial value of 1. Return 1 for an empty list.

from functools import reduce

def product(nums):
    # use reduce with initial value 1
    pass

Recap

  • map(fn, items) transforms every item; wrap in list() to materialise.
  • filter(fn, items) keeps only items where fn(item) is truthy.
  • reduce(fn, items, init) folds items left to right into a single value.
  • Many reduces already exist as built-ins: sum, max, all, str.join.
  • Comprehensions often beat map and filter for readability; learn both and choose by clarity.
  • Iterators are single-use: once exhausted, they yield nothing until recreated.

Next you will see how nested functions can remember values from the scope where they were created, a mechanism called a closure.

Checkpoint quiz

In Python 3, what does map(len, ['a', 'bb']) return?

What is the result of reduce(lambda a, b: a + b, [1, 2, 3], 0)?

Go deeper — technical resources