Back to Blog
Python

Python itertools compress: Filtering with Boolean Masks

python itertools compress: Learn how itertools.compress() filters iterables using parallel boolean masks, with practical examples, comparison to filter(), and edge cases.

itertoolspythonfilteringiteratorsboolean-masks
Diagram showing a data stream passing through a boolean selector mask in Python's itertools.compress

The python itertools compress function pairs each element of a data iterable with a selector from a second iterable and yields only the data elements whose selector is truthy. It is part of the standard library's itertools module, so no extra dependencies are required.

What compress() Does

itertools.compress(data, selectors) accepts two iterables and returns a lazy iterator. When you iterate over it, each element of data is paired with the corresponding element of selectors, and the data element is yielded only when the selector is truthy.

from itertools import compress data = ["alpha", "beta", "gamma", "delta"] selectors = [1, 0, 1, 0] print(list(compress(data, selectors))) # ['alpha', 'gamma']

The returned iterator is consumed lazily, so nothing is computed until you actually iterate. Both arguments can be any iterable: lists, tuples, generators, or even infinite streams.

How Selector Truthiness Works

Selectors are evaluated with Python's standard truthiness rules, not compared against True. Any value Python treats as false — 0, 0.0, "", [], None — suppresses the matching data element. Any truthy value includes it.

data = [10, 20, 30, 40] selectors = [0, 1, "", "yes"] print(list(compress(data, selectors))) # [20, 40]

This is convenient when your mask is already a list of integers or strings rather than explicit booleans. You do not need to normalize the selector values before passing them in.

Stopping at the Shortest Input

compress() stops as soon as either iterable is exhausted. If the selector list is shorter than the data, the remaining data elements are silently dropped.

data = [1, 2, 3, 4, 5] selectors = [1, 0, 1] print(list(compress(data, selectors))) # [1, 3]

The reverse also holds: if data is shorter than selectors, the extra selectors are ignored. This mirrors how zip() handles mismatched lengths, so the behavior is predictable once you know the rule.

Practical Use Cases

Filtering with a Precomputed Boolean Mask

The most common scenario is having a parallel list of booleans computed elsewhere. A list comprehension with zip() works, but compress() expresses the intent more directly.

import itertools records = [ {"id": 1, "status": "active"}, {"id": 2, "status": "archived"}, {"id": 3, "status": "active"}, ] mask = [r["status"] == "active" for r in records] active_records = list(itertools.compress(records, mask))

Combining with Other itertools Tools

compress() pairs naturally with functions that produce selector streams, such as cycle() or islice(). For example, you can select every other element by cycling a mask.

from itertools import compress, cycle data = [10, 20, 30, 40, 50] mask = cycle([1, 0]) print(list(compress(data, mask))) # [10, 30, 50]

compress() vs filter()

filter() applies a predicate function to each element and keeps the element when the predicate returns true. compress() instead uses a parallel iterable of selectors. The distinction matters when the decision about whether to keep an element is already encoded as data rather than computed from the element itself.

Aspectfilter()compress()
Selector typePredicate functionParallel iterable
EvaluationCalls function per elementChecks selector truthiness
TerminationEnd of dataEnd of either input
Typical useValue-based filteringMask-based filtering

If the decision depends only on the element's own value, filter() or a comprehension is usually clearer. If the decision comes from a separate mask, compress() is the better fit.

Performance and Memory Behavior

Because compress() is lazy, it does not build an intermediate list. The selectors are consumed as you iterate, so a generator of selectors works without materializing the full mask.

def selector_stream(): for i in range(1000): yield i % 2 result = compress(range(1000), selector_stream())

This matters when the mask is expensive to compute or when the dataset is large enough that an extra list copy would be noticeable. The tradeoff is that the result is a one-shot iterator: if you need it more than once, you must convert it to a list or tuple.

Edge Cases and Limitations

An empty selector iterable produces an empty result regardless of the data. An infinite selector iterable paired with finite data terminates when the data runs out, so you do not need to slice the selectors to a matching length.

compress() never transforms values; it only selects them. If you need to map or reshape the selected elements, chain map() or a comprehension after it. It also does not accept a predicate function, so there is no way to compute the mask from the data element inside the call itself.

The one-shot nature of the returned iterator is the main practical limitation. If the same filtered sequence is needed in multiple places, materialize it once with list() and reuse that list.

python itertools compress: Practical Usage and Code Examples | RYUSLOG DEV