itertools provides building blocks for efficient looping. Here are the most useful functions.

Why itertools?

  • Memory efficient (lazy evaluation)
  • Fast (implemented in C)
  • Composable (build complex iterators from simple ones)

Combining Iterables

chain

Combine multiple iterables into one:

from itertools import chain
 
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
 
combined = chain(list1, list2, list3)
list(combined)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flat = list(chain.from_iterable(nested))  # [1, 2, 3, 4, 5, 6]

zip_longest

Like zip, but continues until longest iterable:

from itertools import zip_longest
 
names = ["Alice", "Bob"]
scores = [95, 87, 92]
 
list(zip_longest(names, scores, fillvalue="N/A"))
# [('Alice', 95), ('Bob', 87), ('N/A', 92)]

Infinite Iterators

count

Infinite counter:

from itertools import count
 
for i in count(start=10, step=2):
    if i > 20:
        break
    print(i)  # 10, 12, 14, 16, 18, 20

cycle

Repeat forever:

from itertools import cycle
 
colors = cycle(["red", "green", "blue"])
for _, color in zip(range(5), colors):
    print(color)  # red, green, blue, red, green

repeat

Repeat a value:

from itertools import repeat
 
list(repeat("x", 3))  # ['x', 'x', 'x']
 
# Useful with map
list(map(pow, range(5), repeat(2)))  # [0, 1, 4, 9, 16]

Filtering

takewhile / dropwhile

from itertools import takewhile, dropwhile
 
numbers = [1, 3, 5, 7, 2, 4, 6]
 
# Take while condition is true
list(takewhile(lambda x: x < 6, numbers))  # [1, 3, 5]
 
# Drop while condition is true
list(dropwhile(lambda x: x < 6, numbers))  # [7, 2, 4, 6]

filterfalse

Opposite of filter:

from itertools import filterfalse
 
numbers = range(10)
list(filterfalse(lambda x: x % 2, numbers))  # [0, 2, 4, 6, 8]

compress

Filter using a selector:

from itertools import compress
 
data = ["a", "b", "c", "d"]
selectors = [True, False, True, False]
 
list(compress(data, selectors))  # ['a', 'c']

Grouping

groupby

Group consecutive elements:

from itertools import groupby
 
data = [
    {"type": "fruit", "name": "apple"},
    {"type": "fruit", "name": "banana"},
    {"type": "vegetable", "name": "carrot"},
    {"type": "vegetable", "name": "broccoli"},
]
 
# Must be sorted by key first!
for key, group in groupby(data, key=lambda x: x["type"]):
    print(key, list(group))
 
# fruit [{'type': 'fruit', 'name': 'apple'}, ...]
# vegetable [{'type': 'vegetable', 'name': 'carrot'}, ...]

Important: groupby only groups consecutive elements. Sort first!

Slicing

islice

Slice an iterator:

from itertools import islice
 
# Take first 5
list(islice(count(), 5))  # [0, 1, 2, 3, 4]
 
# Skip first 2, take next 3
list(islice(range(10), 2, 5))  # [2, 3, 4]
 
# Every other element
list(islice(range(10), 0, None, 2))  # [0, 2, 4, 6, 8]

Combinatorics

product

Cartesian product:

from itertools import product
 
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
 
list(product(colors, sizes))
# [('red', 'S'), ('red', 'M'), ('red', 'L'),
#  ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]
 
# Repeat same iterable
list(product("AB", repeat=2))
# [('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B')]

permutations

All orderings:

from itertools import permutations
 
list(permutations("ABC", 2))
# [('A', 'B'), ('A', 'C'), ('B', 'A'),
#  ('B', 'C'), ('C', 'A'), ('C', 'B')]
 
list(permutations([1, 2, 3]))
# [(1, 2, 3), (1, 3, 2), (2, 1, 3),
#  (2, 3, 1), (3, 1, 2), (3, 2, 1)]

combinations

Choose without replacement:

from itertools import combinations
 
list(combinations("ABCD", 2))
# [('A', 'B'), ('A', 'C'), ('A', 'D'),
#  ('B', 'C'), ('B', 'D'), ('C', 'D')]

combinations_with_replacement

Choose with replacement:

from itertools import combinations_with_replacement
 
list(combinations_with_replacement("AB", 2))
# [('A', 'A'), ('A', 'B'), ('B', 'B')]

Accumulating

accumulate

Running totals (and more):

from itertools import accumulate
import operator
 
# Running sum
list(accumulate([1, 2, 3, 4]))  # [1, 3, 6, 10]
 
# Running product
list(accumulate([1, 2, 3, 4], operator.mul))  # [1, 2, 6, 24]
 
# Running max
list(accumulate([3, 1, 4, 1, 5, 9], max))  # [3, 3, 4, 4, 5, 9]

Practical Examples

Batch processing

from itertools import islice
 
def batched(iterable, n):
    """Yield n-sized chunks."""
    it = iter(iterable)
    while batch := list(islice(it, n)):
        yield batch
 
list(batched(range(10), 3))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Pairwise iteration

from itertools import pairwise  # Python 3.10+
 
list(pairwise([1, 2, 3, 4]))
# [(1, 2), (2, 3), (3, 4)]
 
# Pre-3.10
from itertools import tee
 
def pairwise_compat(iterable):
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

Generate test data

from itertools import product
 
# All combinations of parameters
params = list(product(
    [True, False],     # enabled
    [1, 10, 100],      # limit
    ["asc", "desc"],   # order
))
# 12 test cases

Quick Reference

from itertools import (
    # Combining
    chain,              # Flatten iterables
    zip_longest,        # Zip with fill value
    
    # Infinite
    count,              # 0, 1, 2, ...
    cycle,              # Repeat sequence
    repeat,             # Repeat value
    
    # Filtering
    takewhile,          # Take while true
    dropwhile,          # Drop while true
    filterfalse,        # Opposite of filter
    compress,           # Filter by selector
    
    # Grouping/Slicing
    groupby,            # Group consecutive
    islice,             # Slice iterator
    
    # Combinatorics
    product,            # Cartesian product
    permutations,       # All orderings
    combinations,       # Choose without replacement
    combinations_with_replacement,
    
    # Accumulating
    accumulate,         # Running totals
)

itertools functions are lazy—they only compute values as needed. Chain them together to build powerful data pipelines.

React to this post: