If you have ever written a deeply nested loop just to combine two lists, or reached for a manual counter inside a while loop, python itertools is about to become your new favourite module. It ships with every Python installation, costs you nothing, and replaces a surprising amount of hand-rolled iteration logic with clean, readable one-liners.
The itertools module python documentation describes it as "functions creating iterators for efficient looping." That is a polite way of saying: stop writing loops by hand when Python already has a better answer.
Throughout this guide you will see exactly when to reach for each tool, how it behaves, and what the output looks like — no fluff, just working code.
Before diving into individual functions, it helps to understand one key idea: every function in the itertools module returns an iterator, not a list. That means values are produced one at a time, on demand. You never pay the memory cost of building the entire result upfront.
import itertools
# This does NOT build a list of a million numbers in memory
counter = itertools.count(start=1)
print(next(counter)) # 1
print(next(counter)) # 2
That lazy evaluation is especially valuable when working with large datasets or combining multiple sequences together.
Python itertools includes three iterators that never stop producing values on their own. You pair them with something like islice or a break condition to control how many values you actually consume.
count(start, step) produces an endless stream of numbers beginning at start, incrementing by step. Think of it as range() that never ends.
import itertools
for n in itertools.count(10, 5):
if n > 35:
break
print(n)
10
15
20
25
30
35
This is useful any time you need to number items in a stream without knowing the total length in advance — for example, tagging lines as they arrive from a file or a socket.
cycle(iterable) loops through a sequence forever. Once it reaches the end, it wraps back around to the beginning.
import itertools
colors = itertools.cycle(["red", "green", "blue"])
for i, color in zip(range(6), colors):
print(f"Item {i}: {color}")
Item 0: red
Item 1: green
Item 2: blue
Item 3: red
Item 4: green
Item 5: blue
A practical use case: assigning round-robin labels, rotating API keys, or alternating row styles in a report.
repeat(value, times) emits the same value over and over. If you omit times, it runs forever.
import itertools
for x in itertools.repeat("hello", 3):
print(x)
hello
hello
hello
repeat is often paired with map or zip when you need to broadcast a single value alongside a sequence.
islice(iterable, stop) or islice(iterable, start, stop, step) lets you slice any iterator the same way you would slice a list — except it works on things that do not support indexing.
import itertools
# Take only the first 4 values from an infinite counter
first_four = list(itertools.islice(itertools.count(100), 4))
print(first_four)
[100, 101, 102, 103]
This is how you safely consume from any of the python infinite iterators shown above. Without islice, list(itertools.count(100)) would run until your machine ran out of memory.
compress(data, selectors) filters a sequence by pairing each element with a corresponding boolean selector. When the selector is truthy, the element passes through; when it is falsy, the element is dropped.
import itertools
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
in_stock = [1, 0, 1, 0, 1]
available = list(itertools.compress(fruits, in_stock))
print(available)
['apple', 'cherry', 'elderberry']
compress is cleaner than a list comprehension with an index whenever your mask already exists as a separate sequence — for example, boolean columns from a data pipeline.
chain(*iterables) connects multiple sequences end-to-end and treats them as one continuous stream. You do not need to copy data into a new combined list first.
import itertools
letters = ["a", "b", "c"]
numbers = [1, 2, 3]
symbols = ["!", "@", "#"]
combined = list(itertools.chain(letters, numbers, symbols))
print(combined)
['a', 'b', 'c', 1, 2, 3, '!', '@', '#']
There is also chain.from_iterable(nested) which unpacks one level of nesting for you:
import itertools
nested = [["cat", "dog"], ["fish", "bird"], ["hamster"]]
flat = list(itertools.chain.from_iterable(nested))
print(flat)
['cat', 'dog', 'fish', 'bird', 'hamster']
Python itertools chain is one of the most frequently used tools when merging results from multiple queries, files, or API pages into a single pass.
This is where python itertools really shines for data science, puzzle solving, and algorithm problems. These three functions generate every possible arrangement or selection from your inputs.
combinations(iterable, r) picks r items at a time from the input, without repetition and without caring about order. ("A", "B", "C") and ("C", "A", "B") count as the same combination.
import itertools
players = ["Alice", "Bob", "Carol", "Dave"]
pairs = list(itertools.combinations(players, 2))
for pair in pairs:
print(pair)
('Alice', 'Bob')
('Alice', 'Carol')
('Alice', 'Dave')
('Bob', 'Carol')
('Bob', 'Dave')
('Carol', 'Dave')
Six pairs from four players — that is the classic "how many handshakes" problem solved in two lines.
permutations(iterable, r) does the same thing but order matters. ("A", "B") and ("B", "A") are counted separately.
import itertools
podium = list(itertools.permutations(["Gold", "Silver", "Bronze"], 2))
for place in podium:
print(place)
('Gold', 'Silver')
('Gold', 'Bronze')
('Silver', 'Gold')
('Silver', 'Bronze')
('Bronze', 'Gold')
('Bronze', 'Silver')
Use permutations any time sequence matters — seating arrangements, password generation, route planning.
product(*iterables, repeat=1) is the Cartesian product — every combination drawn one item from each iterable. This is equivalent to nested for loops but written in a single readable expression.
import itertools
sizes = ["S", "M", "L"]
colors = ["Red", "Blue"]
variants = list(itertools.product(sizes, colors))
for v in variants:
print(v)
('S', 'Red')
('S', 'Blue')
('M', 'Red')
('M', 'Blue')
('L', 'Red')
('L', 'Blue')
The repeat parameter is handy when you want the same iterable crossed with itself multiple times — for example, product("AB", repeat=2) gives all two-character strings from A and B.
groupby(iterable, key) scans a sequence and groups consecutive elements that share the same key value. The key point — and the most common mistake — is that the input must be sorted by the same key first, otherwise non-adjacent equal groups are treated as separate groups.
import itertools
transactions = [
{"date": "2024-01-01", "amount": 50},
{"date": "2024-01-01", "amount": 30},
{"date": "2024-01-02", "amount": 20},
{"date": "2024-01-03", "amount": 80},
{"date": "2024-01-03", "amount": 45},
]
# Sort first, then group
transactions.sort(key=lambda x: x["date"])
for date, group in itertools.groupby(transactions, key=lambda x: x["date"]):
total = sum(t["amount"] for t in group)
print(f"{date}: ${total}")
2024-01-01: $80
2024-01-02: $20
2024-01-03: $125
itertools groupby python is the go-to tool for aggregating sorted data streams — log files, sorted CSV exports, or any pre-ordered query result.
accumulate(iterable, func) applies a function cumulatively across a sequence and yields each intermediate result. By default the function is addition, giving you a running total.
import itertools
import operator
sales = [120, 85, 200, 95, 160]
running_total = list(itertools.accumulate(sales))
print("Running total:", running_total)
running_max = list(itertools.accumulate(sales, func=max))
print("Running max: ", running_max)
Running total: [120, 205, 405, 500, 660]
Running max: [120, 120, 200, 200, 200]
Any binary function works — operator.mul gives you a running product, min tracks the lowest value seen so far.
This example pulls together python itertools chain, product, compress, islice, groupby, combinations, and accumulate into one script that processes a small product catalogue.
import itertools
# --- Data Setup ---
categories = ["Electronics", "Clothing"]
sizes = ["S", "M", "L"]
available_flags = [1, 0, 1, 1, 0, 1]
# itertools.product: generate SKU candidates from categories x sizes
sku_candidates = list(itertools.product(categories, sizes))
print("All SKU candidates:")
for sku in sku_candidates:
print(" ", sku)
print()
# itertools.compress: keep only in-stock SKUs
in_stock_skus = list(itertools.compress(sku_candidates, available_flags))
print("In-stock SKUs:")
for sku in in_stock_skus:
print(" ", sku)
print()
# itertools.combinations: unique pairs of in-stock SKUs for bundle promotions
bundles = list(itertools.combinations(in_stock_skus, 2))
print(f"Possible bundle pairs: {len(bundles)}")
for bundle in itertools.islice(bundles, 4): # show first 4 only
print(" ", bundle)
print()
# itertools.chain: merge two separate order lists into one stream
morning_orders = [("Electronics", "S"), ("Clothing", "L")]
afternoon_orders = [("Electronics", "L"), ("Clothing", "S"), ("Electronics", "M")]
all_orders = list(itertools.chain(morning_orders, afternoon_orders))
print("All orders (merged):", all_orders)
print()
# itertools.groupby: tally orders by category (must sort first)
all_orders.sort(key=lambda x: x[0])
print("Orders grouped by category:")
for category, group in itertools.groupby(all_orders, key=lambda x: x[0]):
items = list(group)
print(f" {category}: {len(items)} orders -> {items}")
print()
# itertools.accumulate: running order count
order_counts = [len(morning_orders), 1, 1, len(afternoon_orders) - 2]
running = list(itertools.accumulate(order_counts))
print("Running order count checkpoints:", running)
All SKU candidates:
('Electronics', 'S')
('Electronics', 'M')
('Electronics', 'L')
('Clothing', 'S')
('Clothing', 'M')
('Clothing', 'L')
In-stock SKUs:
('Electronics', 'S')
('Electronics', 'L')
('Clothing', 'S')
('Clothing', 'L')
Possible bundle pairs: 6
(('Electronics', 'S'), ('Electronics', 'L'))
(('Electronics', 'S'), ('Clothing', 'S'))
(('Electronics', 'S'), ('Clothing', 'L'))
(('Electronics', 'L'), ('Clothing', 'S'))
All orders (merged): [('Electronics', 'S'), ('Clothing', 'L'), ('Electronics', 'L'), ('Clothing', 'S'), ('Electronics', 'M')]
Orders grouped by category:
Clothing: 2 orders -> [('Clothing', 'L'), ('Clothing', 'S')]
Electronics: 3 orders -> [('Electronics', 'S'), ('Electronics', 'L'), ('Electronics', 'M')]
Running order count checkpoints: [2, 3, 4, 5]
Every tool in the itertools module python follows the same philosophy: return a lazy iterator, compose cleanly with other iterators, and keep your code readable. Once you start reaching for python itertools instead of manual loops, you will find yourself writing less code that does more — and that is exactly what the module was designed for.