itertools Module — Core Concepts

Why itertools Exists

Python’s for loop handles simple iteration well, but real-world data processing often requires combining, slicing, grouping, or generating sequences in ways that would need nested loops and temporary lists. itertools provides memory-efficient building blocks for these patterns — all implemented in C for speed.

The key principle: itertools functions return iterators, not lists. They produce values on demand, so they work with datasets that don’t fit in memory.

The Three Categories

1. Infinite Iterators

These never stop — you must limit them yourself:

count(start, step) — counts forever: 10, 11, 12, 13, ... cycle(iterable) — repeats endlessly: A, B, C, A, B, C, ... repeat(value, times) — repeats a value: 7, 7, 7, ... (optionally limited)

from itertools import count, cycle, islice

# Generate IDs starting from 1000
ids = count(1000)
next(ids)  # 1000
next(ids)  # 1001

# Alternate team assignments
teams = cycle(["Red", "Blue", "Green"])
assignments = [next(teams) for _ in range(7)]
# ['Red', 'Blue', 'Green', 'Red', 'Blue', 'Green', 'Red']

2. Combinatoric Iterators

Generate arrangements of elements:

product(*iterables) — Cartesian product (all combinations across multiple sets) permutations(iterable, r) — ordered arrangements combinations(iterable, r) — unordered selections (no repeats) combinations_with_replacement(iterable, r) — unordered selections (repeats allowed)

from itertools import product, combinations

# All possible dice rolls (two dice)
rolls = list(product(range(1, 7), repeat=2))  # 36 outcomes

# Choose 2 toppings from 4
toppings = ["pepperoni", "mushroom", "olive", "pepper"]
pairs = list(combinations(toppings, 2))  # 6 pairs

3. Terminating Iterators

These consume finite input and produce transformed output:

chain(*iterables) — concatenate iterators:

from itertools import chain

all_items = chain([1, 2], [3, 4], [5])  # 1, 2, 3, 4, 5

islice(iterable, stop) or islice(iterable, start, stop, step) — slice an iterator:

from itertools import islice

# First 5 items from an infinite source
first_five = list(islice(count(), 5))  # [0, 1, 2, 3, 4]

groupby(iterable, key) — group consecutive elements:

from itertools import groupby

data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))
# A [('A', 1), ('A', 2)]
# B [('B', 3), ('B', 4)]
# A [('A', 5)]

Important: groupby only groups consecutive items. Sort first if you want all items with the same key together.

starmap(function, iterable) — applies a function to unpacked arguments:

from itertools import starmap

pairs = [(2, 3), (4, 5), (6, 7)]
products = list(starmap(lambda a, b: a * b, pairs))  # [6, 20, 42]

takewhile / dropwhile — take or skip items based on a condition:

from itertools import takewhile, dropwhile

nums = [2, 4, 6, 7, 8, 10]
evens = list(takewhile(lambda x: x % 2 == 0, nums))  # [2, 4, 6]
rest = list(dropwhile(lambda x: x % 2 == 0, nums))   # [7, 8, 10]

Composing itertools Functions

The real power comes from chaining them together:

from itertools import chain, islice, cycle

# Distribute 10 tasks across 3 workers, round-robin
workers = cycle(["Alice", "Bob", "Charlie"])
tasks = range(10)
assignments = list(zip(tasks, islice(workers, 10)))
# [(0, 'Alice'), (1, 'Bob'), (2, 'Charlie'), (3, 'Alice'), ...]

Common Misconception

“itertools is only for math-heavy or data science work.” In practice, chain, islice, groupby, and product show up constantly in web development, file processing, configuration generation, and testing. If you’re writing nested loops to combine things, there’s probably an itertools function for it.

Quick Reference

FunctionWhat It DoesMemory
chainConcatenate iteratorsO(1)
isliceSlice an iteratorO(1)
groupbyGroup consecutive itemsO(1) per group
productCartesian productO(1) per item
combinationsChoose r itemsO(r)
permutationsArrange r itemsO(r)
countInfinite counterO(1)
cycleInfinite repeatO(n) for source

One thing to remember: itertools turns nested loops into clean, composable pipelines. Everything is lazy — no memory wasted on intermediate lists. When you find yourself writing for x in A: for y in B:, check if product(A, B) does the job.

pythonstandard-libraryfunctional

See Also

  • Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
  • Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
  • Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
  • Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
  • Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.