Use Python `itertools` for Iteration

Learn how to use Python's itertools module for efficient iteration with chain, cycle, product, combinations, groupby, and more.

7 min read

The itertools module in the Python standard library provides a collection of fast, memory-efficient tools for working with iterators. Use itertools to combine, filter, and transform sequences without writing deeply nested loops or building intermediate lists.

pythonpython
from itertools import chain
 
letters = ["a", "b"]
numbers = [1, 2, 3]
combined = list(chain(letters, numbers))
print(combined)   # ['a', 'b', 1, 2, 3]

chain concatenates multiple iterables into a single iterator. The result is computed on demand; list() materializes it for printing.

Infinite iterators

Three itertools functions produce infinite sequences that never run out of values on their own, so they need to be paired with something that stops them. Use islice to take a limited slice from the front of an infinite iterator.

pythonpython
from itertools import count, cycle, repeat, islice
 
print(list(islice(count(10, 2), 5)))    # [10, 12, 14, 16, 18]
print(list(islice(cycle("AB"), 6)))     # ['A', 'B', 'A', 'B', 'A', 'B']
print(list(repeat("Hi", 3)))              # ['Hi', 'Hi', 'Hi']

count(start, step) counts up from start by step. cycle(seq) repeats a sequence endlessly. repeat(value, n) repeats a value n times.

Always pair infinite iterators with islice or a loop condition.

Combining iterables

These functions combine multiple iterables in different ways, from simple concatenation to pairing elements position by position.

pythonpython
from itertools import chain, zip_longest
 
list_a = [1, 2, 3]
list_b = ["a", "b"]
 
print(list(chain(list_a, list_b)))                                    # [1, 2, 3, 'a', 'b']
print(list(zip(list_a, list_b)))                                        # [(1, 'a'), (2, 'b')]
print(list(zip_longest(list_a, list_b, fillvalue=None)))  # [(1, 'a'), (2, 'b'), (3, None)]

zip_longest pairs elements from both iterables, filling missing values with fillvalue when one iterable is shorter. By contrast, built-in zip() stops at the shortest iterable.

product: Cartesian products

product generates every ordered combination of items from the input iterables, the same result you would get from writing one nested for loop per iterable.

pythonpython
from itertools import product
 
ranks = ["A", "K", "Q"]
suits = ["hearts", "spades"]
 
for rank, suit in product(ranks, suits):
    print(f"{rank} of {suit}")

Each rank is paired with every suit in turn, producing all six rank-suit combinations in order, the same way a full deck's worth of card labels would be generated:

texttext
A of hearts
A of spades
K of hearts
K of spades
Q of hearts
Q of spades

Use repeat=N to take the Cartesian product of an iterable with itself N times, which is a compact way to generate every fixed-length combination of a small alphabet:

pythonpython
from itertools import product
 
print(list(product("01", repeat=3)))

Every 3-bit combination of "0" and "1" is generated, in the same order three nested loops over the same two characters would produce:

texttext
[('0', '0', '0'), ('0', '0', '1'), ('0', '1', '0'), ('0', '1', '1'),
 ('1', '0', '0'), ('1', '0', '1'), ('1', '1', '0'), ('1', '1', '1')]

product("01", repeat=3) generates all 3-bit binary strings. This is equivalent to three nested loops.

combinations and permutations

These functions generate subsets and arrangements from an iterable, covering the three most common combinatorics needs: unordered subsets, ordered arrangements, and subsets that allow repeats.

pythonpython
from itertools import combinations, permutations, combinations_with_replacement
 
items = ["A", "B", "C"]
 
print(list(combinations(items, 2)))
print(list(permutations(items, 2)))
print(list(combinations_with_replacement(items, 2)))

combinations skips reversed pairs like (B, A), permutations includes them, and combinations_with_replacement adds pairs of the same item like (A, A):

texttext
[('A', 'B'), ('A', 'C'), ('B', 'C')]
[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
FunctionOrder matters?Allows repeats?Example output
combinations(items, 2)NoNo(A,B), (A,C), (B,C)
permutations(items, 2)YesNo(A,B), (A,C), (B,A), ...
combinations_with_replacement(items, 2)NoYes(A,A), (A,B), (A,C), ...

groupby: grouping consecutive elements

groupby groups consecutive elements that share the same key. The input must be sorted by the key you are grouping on.

pythonpython
from itertools import groupby
 
logs = [
    ("ERROR", "Disk full"),
    ("INFO", "Server started"),
    ("ERROR", "Timeout"),
    ("INFO", "User login"),
]
 
for level, entries in groupby(sorted(logs), key=lambda x: x[0]):
    print(f"{level}:")
    for entry in entries:
        print(f"  {entry[1]}")

Sorting the logs first groups every ERROR entry together and every INFO entry together, regardless of their original order in the list:

texttext
ERROR:
  Disk full
  Timeout
INFO:
  Server started
  User login

groupby returns (key, group_iterator) pairs. The group iterator yields consecutive elements with the same key. If the input is not sorted, disconnected runs with the same key become separate groups.

Slicing and filtering iterators

islice slices an iterator by position, the same way list slicing works but without needing the whole sequence in memory first. dropwhile and takewhile slice by condition instead of position.

pythonpython
from itertools import islice, dropwhile, takewhile
 
nums = range(20)
 
print(list(islice(nums, 5, 10, 2)))   # [5, 7, 9]
 
values = [1, 3, 5, 2, 4, 6]
print(list(takewhile(lambda x: x < 4, values)))   # [1, 3]
print(list(dropwhile(lambda x: x < 4, values)))    # [5, 2, 4, 6]

takewhile yields elements while the condition is true, then stops. dropwhile skips elements while the condition is true, then yields the rest.

accumulate: running totals

accumulate yields accumulated results. By default it sums, but you can pass any binary function.

pythonpython
from itertools import accumulate
import operator
 
nums = [1, 2, 3, 4, 5]
 
print(list(accumulate(nums)))                            # [1, 3, 6, 10, 15]
print(list(accumulate(nums, operator.mul)))   # [1, 2, 6, 24, 120]

accumulate(nums) produces running sums. accumulate(nums, operator.mul) produces running products. This is useful for cumulative statistics, running totals, or any progressive computation.

Practical example: generating test data

Use itertools to generate combinations of test parameters, covering every browser, screen size, and theme combination without writing three nested loops by hand.

pythonpython
from itertools import product
 
browsers = ["chrome", "firefox", "safari"]
sizes = ["mobile", "tablet", "desktop"]
themes = ["light", "dark"]
 
test_cases = list(product(browsers, sizes, themes))
print(f"Total test cases: {len(test_cases)}")
for browser, size, theme in test_cases[:3]:
    print(f"  {browser} / {size} / {theme}")

3 browsers times 3 sizes times 2 themes gives 18 total combinations, and the loop prints just the first three to keep the output short:

texttext
Total test cases: 18
  chrome / mobile / light
  chrome / mobile / dark
  chrome / tablet / light

product(browsers, sizes, themes) generates every combination of browser, screen size, and theme. This pattern is useful for parameterized testing, configuration matrix generation, and scenario planning.

Common mistakes

Using groupby on unsorted data. groupby only groups consecutive matching elements. Sort the iterable by the same key you plan to group on, or use defaultdict(list) from collections for unsorted grouping.

Forgetting that itertools produce iterators. combinations("ABC", 2) returns an iterator, not a list. Call list() if you need to index, slice, or iterate multiple times.

Materializing large combinations. product(range(1000), repeat=3) generates a billion tuples. Never call list() on a huge combinatorial result. Iterate lazily instead.

Using accumulate with the wrong operator. The default is operator.add. Pass operator.mul, max, min, or a custom function for non-additive accumulation.

Rune AI

Rune AI

Key Insights

  • itertools.chain(*iterables) concatenates multiple iterables into a single iterator.
  • itertools.product(A, B) generates the Cartesian product of two or more iterables.
  • itertools.combinations(seq, r) generates all r-length combinations without repeats.
  • itertools.groupby(seq, key) groups consecutive elements by a key function.
  • itertools.islice(seq, start, stop, step) slices an iterator like list slicing.
  • All itertools functions return iterators; wrap with list() to materialize results.
RunePowered by Rune AI

Frequently Asked Questions

When should I use itertools instead of a for loop?

Use `itertools` when you need to combine, filter, or transform iterables in ways that would require nested loops, temporary lists, or complex state management. `itertools` functions are implemented in C, so they are also faster for large datasets.

What is the difference between product and combinations?

`product(*iterables, repeat=N)` generates the Cartesian product -- all possible ordered tuples, including repeats like (A,A). `combinations(iterable, r)` generates unordered tuples of length r without repeats. `permutations` is like combinations but order matters.

Are itertools iterators reusable?

No. Like all Python iterators, itertools results are consumed once. Wrap them in `list()` if you need to iterate multiple times, but be aware that materializing the full result can use significant memory for large outputs.

Conclusion

itertools is the standard library module for efficient, memory-friendly iteration. Use chain to flatten iterables, product for Cartesian products, combinations and permutations for combinatorics, and groupby for grouping sorted data. Each function returns an iterator, so results are computed on demand.