Pythonitertools

itertools

The itertools module is part of Python's standard library and provides a set of fast, memory-efficient "building block" functions for working with iterators. Instead of building intermediate lists in memory, these tools produce values lazily, one at a time, which makes them ideal for looping over large — or even infinite — sequences of data. Once you're comfortable with the basics of iteration, itertools becomes one of the most useful modules for writing clean, efficient Python.

In this page we'll cover infinite iterators (count, cycle, repeat), combining iterables with chain, combinatorics with combinations and permutations, grouping consecutive items with groupby, and finish with a worked example that combines several of these tools together.

Infinite Iterators: `count()`, `cycle()`, `repeat()`

Three functions in itertools produce values forever, without ever raising StopIteration on their own:

  • count(start=0, step=1) — counts up (or down) indefinitely, like an infinite range().

  • cycle(iterable) — repeats the items of an iterable over and over, forever.

  • repeat(value, times=None) — yields the same value repeatedly, forever unless times is given.

Warning
These three iterators never stop on their own. If you loop over one directly with a plain `for` loop and no exit condition, your program will hang forever:

Python
from itertools import count

# DANGER: this loop never ends!
for n in count(1):
    print(n)
You must always pair an infinite iterator with something that limits it — either `itertools.islice()` to take only a fixed number of items, or an explicit `break` condition inside the loop.

The safe way to take a limited number of values from an infinite iterator is itertools.islice(), which works like slicing but for any iterator:

Python
from itertools import count, islice

# Take only the first 5 values — this terminates safely.
first_five = list(islice(count(1), 5))
print(first_five)

# count() also supports a step, just like range()
evens = list(islice(count(0, 2), 5))
print(evens)
[1, 2, 3, 4, 5]
[0, 2, 4, 6, 8]

cycle() is handy for round-robin style logic — for example assigning tasks to a fixed set of workers in rotation:

Python
from itertools import cycle, islice

workers = cycle(["Alice", "Bob", "Carol"])
tasks = ["task-1", "task-2", "task-3", "task-4", "task-5"]

for task, worker in zip(tasks, workers):
    print(f"{task} -> {worker}")
task-1 -> Alice
task-2 -> Bob
task-3 -> Carol
task-4 -> Alice
task-5 -> Bob

Notice that zip() here is what keeps things safe — it stops once the shorter iterable (tasks) is exhausted, even though cycle(workers) would otherwise run forever. repeat() is often used to supply a constant argument to functions like map():

Python
from itertools import repeat

squares_of_five = list(map(pow, repeat(5, 3), [1, 2, 3]))
print(squares_of_five)  # 5**1, 5**2, 5**3
[5, 25, 125]
`chain()`: Combining Iterables Without Copying

itertools.chain() takes several iterables and walks through them one after another as if they were a single sequence — without ever building a new combined list in memory. This is more efficient than writing list1 + list2 when you just need to iterate, especially for large sequences or generators.

Python
from itertools import chain

fruits = ["apple", "banana"]
vegetables = ["carrot", "potato"]
grains = ("rice", "oats")

for item in chain(fruits, vegetables, grains):
    print(item)
apple
banana
carrot
potato
rice
oats
Tip
`chain()` accepts any mix of iterable types — lists, tuples, sets, generators — as long as each one is iterable. It doesn't care that they're different types.
`combinations()` vs `permutations()`

Both functions pick groups of items from an iterable, but they answer different questions:

  • combinations(iterable, r) — all ways to choose r items where order does not matter. (A, B) and (B, A) count as the same combination, so only one is produced.

  • permutations(iterable, r) — all ways to choose r items where order matters. (A, B) and (B, A) are counted separately.

Here's the difference made concrete with three letters, picking 2 at a time:

Python
from itertools import combinations, permutations

letters = ["A", "B", "C"]

print("combinations:", list(combinations(letters, 2)))
print("permutations:", list(permutations(letters, 2)))
combinations: [('A', 'B'), ('A', 'C'), ('B', 'C')]
permutations: [('A', 'B'), ('B', 'A'), ('A', 'C'), ('C', 'A'), ('B', 'C'), ('C', 'B')]

combinations() produced 3 pairs; permutations() produced 6, because it counts (A, B) and (B, A) as different results. A good rule of thumb: if swapping the order of two chosen items gives you a genuinely different outcome (like first place vs. second place in a race), use permutations(). If it doesn't (like two people paired up to work together), use combinations().

`groupby()`: Grouping Consecutive Items

itertools.groupby() groups consecutive items in an iterable that share the same key. This is a common source of bugs: groupby() does not scan the whole sequence looking for matches — it only groups items that are already next to each other.

Warning
The input to `groupby()` must already be sorted (or otherwise arranged) by the same key you're grouping on. If it isn't, equal keys that are separated by other values will be split into multiple separate groups instead of one.

Python
from itertools import groupby

# NOT sorted by first letter - "avocado" and "apple" are separated by "banana"
words = ["apple", "banana", "avocado", "cherry"]

for key, group in groupby(words, key=lambda w: w[0]):
    print(key, list(group))
a ['apple']
b ['banana']
a ['avocado']
c ['cherry']

Notice "a" appears twice — once for "apple" and again for "avocado" — because they weren't adjacent. Sorting first fixes this:

Python
from itertools import groupby

words = ["apple", "banana", "avocado", "cherry"]
words_sorted = sorted(words, key=lambda w: w[0])

for key, group in groupby(words_sorted, key=lambda w: w[0]):
    print(key, list(group))
a ['apple', 'avocado']
b ['banana']
c ['cherry']
Note
Each `group` returned by `groupby()` is itself an iterator, and it becomes invalid as soon as you advance to the next group. That's why the examples above wrap each group in `list(...)` immediately — if you tried to save the groups for later without doing this, you'd find they've already been consumed.
Worked Example: Tournament Pairings

Let's combine a couple of these tools in a small, realistic scenario. Suppose we're running a round-robin chess tournament and need every unique pair of players who will face each other, split by which skill division they're in. We'll use groupby() to split players into divisions, and combinations() to generate the unique pairs within each division.

Python
from itertools import combinations, groupby

players = [
    ("Ada", "Advanced"),
    ("Liu", "Advanced"),
    ("Sam", "Advanced"),
    ("Ravi", "Beginner"),
    ("Mia", "Beginner"),
]

# Must be sorted by division first for groupby() to work correctly.
players_sorted = sorted(players, key=lambda p: p[1])

for division, group in groupby(players_sorted, key=lambda p: p[1]):
    names = [name for name, _ in group]
    pairs = list(combinations(names, 2))
    print(f"{division} division ({len(names)} players, {len(pairs)} matches):")
    for pair in pairs:
        print(f"  {pair[0]} vs {pair[1]}")
Advanced division (3 players, 3 matches):
  Ada vs Liu
  Ada vs Sam
  Liu vs Sam
Beginner division (2 players, 1 matches):
  Ravi vs Mia

This is exactly the kind of task itertools shines at: no manual nested loops, no intermediate lists of duplicated pairs to filter — just composing small, well-tested pieces together.

Quick Reference

Function

Purpose

Stops on its own?

count(start, step)

Infinite counting sequence

No — use islice or break

cycle(iterable)

Repeats an iterable forever

No — use islice or break

repeat(value, times)

Repeats a value (optionally n times)

Only if times is given

chain(*iterables)

Walks multiple iterables as one sequence

Yes, when all are exhausted

combinations(iterable, r)

Unordered selections of size r

Yes

permutations(iterable, r)

Ordered selections of size r

Yes

groupby(iterable, key)

Groups consecutive items sharing a key

Yes

  • itertools functions return lazy iterators, not lists — wrap in list(...) when you need to see or store all the results.

  • Always bound infinite iterators (count, cycle, repeat without times) with islice(), zip() against a finite iterable, or an explicit break.

  • groupby() only groups adjacent matching items — sort your data by the grouping key first.