Skip to main content
blog

map(), filter(), and reduce() in Python Explained

February 10, 2023 · 851 words · 4 min read

map(), filter(), and reduce() in Python ExplainedImage unavailableTry again
Table of Contents
Preparing narration.

The title calls these functions powerful, but the useful part is much less dramatic. map(), filter(), and reduce() each describe one shape of work over a collection. If you can name the shape, you can decide whether one of them makes the code clearer or whether a normal loop is the better answer.

All three accept a function as an argument. A function that receives another function is called a higher-order function, which sounds academic until you see the three questions they answer:

  • Should every item become a new value?
  • Should some items be kept and the rest discarded?
  • Should many values become one result?

Transforming every item with map

map(function, iterable) applies the function to each item. It returns a lazy iterator, so wrap it in list() when you need all results at once:

Text
map(function, iterable)
Python
numbers = [1, 2, 3, 4, 5]
doubled = map(lambda x: x * 2, numbers)
print(list(doubled))

The lambda receives one number at a time. map() passes each number through the multiplication, and list() consumes the iterator to produce [2, 4, 6, 8, 10].

If the transformation already exists as a function, pass that function directly:

Python
names = ["asha", "mina", "rohan"]
upper_names = map(str.upper, names)
 
for name in upper_names:
    print(name)

There is no need to write lambda name: name.upper() here. The direct function keeps the operation visible, and the loop consumes the lazy iterator one item at a time.

The iterator is single-use:

Python
numbers = [1, 2, 3]
doubled = map(lambda number: number * 2, numbers)
print(list(doubled))
print(list(doubled))

The second print is [] because the iterator has already been consumed. That surprises people when they store a map() result and expect it to behave like a list.

For a simple transformation, I usually prefer a list comprehension because the result type and rule are obvious at a glance:

Python
doubled = [number * 2 for number in numbers]

Keeping matching items with filter

filter(predicate, iterable) keeps an item when the predicate returns a truthy value. A predicate is simply a function used to answer a yes-or-no question:

Text
filter(predicate, iterable)
Python
numbers = [1, 2, 3, 4, 5]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))

The predicate returns True for even values, so the result is [2, 4]. A comprehension expresses the same rule directly and is often easier to debug:

Python
evens = [number for number in numbers if number % 2 == 0]

Give a predicate a name when the condition carries business meaning:

Python
def is_available(product):
    return product["stock"] > 0 and not product["archived"]
 
 
products = [
    {"name": "Notebook", "stock": 3, "archived": False},
    {"name": "Pen", "stock": 0, "archived": False},
]
available = list(filter(is_available, products))

The named function gives you a place to test the rule independently. A lambda is fine for number % 2 == 0; it becomes a distraction when the condition needs another explanation.

Combining values with reduce

reduce() repeatedly combines two values until one result remains. Unlike map() and filter(), it is not a built-in name, so import it from functools:

Text
reduce(function, iterable)
Python
from functools import reduce
 
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda left, right: left + right, numbers)
print(total)

The first call combines 1 and 2, producing 3. The next call combines that result with 3, then continues until the total is 15. This is a left-to-right chain, not a mysterious shortcut.

For addition, sum(numbers) is clearer. reduce() also needs a decision for an empty iterable. Without an initial value, reduce() raises TypeError when there is nothing to combine:

Python
from functools import reduce
 
print(reduce(lambda left, right: left + right, [], 0))

The final 0 is the initial value, so this version prints 0.

You can also combine values into something other than a number, but check whether a normal operation says the same thing more clearly. For example, a sentence is better built with " ".join(words) than with a reduction that keeps adding strings. reduce() earns its place when the repeated combination is the useful idea, not merely because it can express the answer.

The three functions can form a pipeline when each step has a separate job:

Python
prices = [5, 12, 20, 3]
eligible = filter(lambda price: price >= 5, prices)
with_tax = map(lambda price: price * 1.18, eligible)
total = sum(with_tax)
print(total)

This works because filter() and map() stay lazy until sum() consumes them. If the callbacks start needing several lines or side effects, stop and write a loop. A little repetition is easier to inspect than a pipeline that hides the state changes.

My caveat is that nested reductions can make a small calculation harder to debug than a normal loop. map() and filter() are fine when their iterator behavior is clear. For many everyday transformations, comprehensions read better. Use reduce() when the repeated combination is genuinely the point, then name the operation clearly.

The judgment is straightforward: use map() to change every value, filter() to select values, and reduce() to collapse values into one result. But short syntax is not automatically readable syntax. If a loop explains the rule faster, write the loop.

Thank you banner imageImage unavailableTry againThank you banner image

pythonfunctional-programmingmap-filter-reduceprogramming