itertools
In short: itertools gives lazy building blocks for combining and slicing
iterables without materialising an extra list first. chain(a, b) walks several
iterables end to end as if they were one. product(a, b) gives every pairing of
two iterables — nested loops, flattened into one call. groupby collapses runs
of equal neighbours into groups; it does not group the whole sequence by key
unless you sort first. islice slices an iterator the way [start:stop:step]
slices a list, which matters because a generator can't be indexed at all. Reach for
one of these before writing the nested loop or nested nested-if by hand.

Combine two menus into one listing
A breakfast menu and a lunch menu need to print as one combined list. + only
works when both sides are the same concrete type — it breaks the moment one side is
a generator instead of a list:
breakfast = ["idli", "dosa"]
lunch = (item for item in ["rice", "dal"]) # a generator, not a list
print(breakfast + lunch)chain walks any iterables end to end, lazily — it never builds the combined
sequence in memory before you start reading it:
Every outfit combination
A shop wants every size/colour combination it stocks, to generate a page for each.
Nested loops do it, but product says the same thing in one line:
product also takes repeat= for combining one iterable with itself — every
sequence of n coin flips, say:
Group consecutive log levels
A log file's levels need collapsing into runs — how many INFO lines in a row,
before the next ERROR. groupby does exactly that, but only for neighbours:
from itertools import groupby
logs = ["INFO", "INFO", "ERROR", "INFO"]
groups = [key for key, _ in groupby(logs)]
print(groups)If you want every INFO merged into a single group regardless of where it sits,
sorted() first — groupby only ever sees what's already next to each other:
One more: islice
A generator has no [5:10] — it isn't indexable. islice slices it anyway,
without turning it into a list first (which would defeat the point of a generator
that might never end):
Quick practice
from itertools import chain
a = [1, 2]
b = [3, 4]
combined = chain(a, b)
print(list(combined), list(combined))TypeErrorRun it and read the error. Fix the line so it actually counts the outcomes.
🎯 Your turn
Write menu_combinations(mains, sides): return every [main, side] pairing as a
list of two-item lists, using itertools.product.
menu_combinations(["dosa", "idli"], ["chutney", "sambar"])→[["dosa", "chutney"], ["dosa", "sambar"], ["idli", "chutney"], ["idli", "sambar"]]menu_combinations([], ["chutney"])→[]
Hint: product(mains, sides) yields (main, side) tuples — turn each into a
list: [list(pair) for pair in product(mains, sides)].
Press ▶ Run and hit ✓ Check. ✅
