Skip to content
← All Python articles

Python explainer

Combine a List into One Value with reduce

About 4 min to read. Allow extra time to work through examples.

See how reduce combines items left to right, when sum or join is clearer, and what to do with an empty list.

This article uses Python. You can make it your practice mode to browse related content across the site.

Each user's permissions are a list of bit flags, and you want one combined mask. The functools.reduce function combines a list into one value by calling a two-argument function over and over:

from functools import reduce
from operator import or_

masks = [1, 4, 2]
print(reduce(or_, masks, 0))  # 7

The operator module has a function for each operator, and or_ is the function form of |. Starting from 0, reduce computes or_(0, 1), then or_(1, 4), then or_(5, 2). Each step passes the running result and the next item, from left to right. So the order matters for an operation such as subtraction:

from operator import sub

print(reduce(sub, [10, 3, 2]))      # 5, from (10 - 3) - 2
print(reduce(sub, [10, 3, 2], 20))  # 5, from ((20 - 10) - 3) - 2

Use a built-in when one fits

Most uses of reduce have a clearer built-in:

Goal Use
Add numbers sum(values)
Multiply numbers math.prod(values)
Find the smallest or largest min(values), max(values)
Check truth values any(values), all(values)
Join strings "".join(parts)
Flatten one level of lists list(chain.from_iterable(groups))
Merge dictionaries, later values winning A loop that calls update

Some built-ins also do less work. The any and all functions stop at the first item that decides the answer, while reduce always reads the whole input. If the combining step needs several statements, an early exit or error handling, write a loop.

Decide what an empty list means

Without a starting value, reduce uses the first item as the start. A list with one item gives that item back unchanged, and an empty list raises TypeError. With a starting value, an empty list gives back the starting value:

empty: list[int] = []
print(reduce(or_, empty, 0))  # 0

Pick a starting value that leaves the result unchanged, such as 0 for | and +, or 1 for *. If an empty list means a bug in your program, leave the starting value out and let reduce raise. Do not invent a default only to avoid the error.

The starting value can also have a different type from the items:

def append_label(text: str, value: int) -> str:
    return f"{text}[{value}]"

print(reduce(append_label, [1, 2, 3], "items:"))  # items:[1][2][3]

Here the running result is a str and each item is an int, and mypy checks that the function and the starting value agree.

Adding lists copies them again and again

On a list of lists, reduce(operator.add, chunks) builds a new list at every step and copies everything collected so far into it. With 1,000 chunks of 10 items, the last step alone copies about 10,000 items, and the whole call copies about 5 million. Flatten in one pass instead:

from itertools import chain

chunks = [[1, 2], [3], [], [4, 5]]
print(list(chain.from_iterable(chunks)))  # [1, 2, 3, 4, 5]

That visits each item once, and so does a loop that calls extend on one list. Building a long string with + has the same problem, so use "".join(parts). A call such as sum(chunks, []) copies the same way reduce does.

Counter addition drops counts

You met Counter in the first article of this track. Adding two Counter objects with + throws away zero and negative counts at every step. The update method adds counts in place and keeps them. So switching from one to the other can change the answer:

from collections import Counter
from operator import add

counters = [Counter({"x": -2}), Counter({"x": 3})]
added = reduce(add, counters, Counter[str]())
total: Counter[str] = Counter()
for counter in counters:
    total.update(counter)
print(added, total)  # Counter({'x': 3}) Counter({'x': 1})

For counts that are never negative, the two agree, and update avoids building a new Counter at each step. When negative counts mean something, decide whether to keep them before you pick one.

Check your understanding A call to reduce(operator.add, list_of_lists) works in your tests but is slow on a million chunks. Why, and what should you use instead? Show answer

Each + builds a new list and copies everything collected so far, so the copying grows with the square of the number of chunks. Use list(chain.from_iterable(list_of_lists)), or a loop that calls extend on one list. Both copy each item once.

Going further

Since Python 3.14, you can pass the starting value by keyword, as initial=0. Code that must also run on 3.12 passes it by position.

step 35 of 55

Everyday Standard Library Tools

Up next Make Objects Sortable with total_ordering