itertools Interview Questions & Answers
7 questions Updated 2026-06-18
Python interview questions on itertools — count/cycle/repeat, chain, islice, combinations/permutations/product, groupby's sorted-input rule, accumulate, and the memory benefit of lazy iterators.
These are the three infinite iterators. count(start, step) yields an
endless arithmetic sequence. cycle(iterable) repeats an iterable's items
forever. repeat(value, times) yields the same value endlessly, or times
times if given.
from itertools import count, cycle, repeat, islice
list(islice(count(10, 2), 3)) # [10, 12, 14]
list(islice(cycle("AB"), 5)) # ['A', 'B', 'A', 'B', 'A']
list(repeat(7, 3)) # [7, 7, 7]
Because count and cycle never stop, you must bound them — with islice, zip,
or a break — or your loop runs forever. They're ideal for generating ids,
round-robin assignment, or padding.
chain(*iterables) lazily concatenates multiple iterables into one stream,
without building an intermediate combined list. chain.from_iterable(iter_of_iters)
does the same when the iterables come from a single iterable (e.g. a list of lists).
from itertools import chain
list(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
rows = [[1, 2], [3, 4], [5, 6]]
list(chain.from_iterable(rows)) # [1, 2, 3, 4, 5, 6] — flatten one level
It's the memory-friendly way to iterate over several sequences as if they were one, and the idiomatic one-level flatten.
islice(iterable, stop) or islice(iterable, start, stop, step) slices any
iterator lazily — including infinite ones and generators that don't support
[ ] indexing. Unlike list slicing, it can't use negative indices (it can't
look backward in a stream) and it consumes the underlying iterator.
from itertools import islice, count
list(islice(count(), 2, 7)) # [2, 3, 4, 5, 6] — works on an infinite source
gen = (x * x for x in range(10))
list(islice(gen, 3)) # [0, 1, 4] — slice a generator
Use islice to take a window from a stream without materializing it. For a concrete
list where you want negative indices, ordinary seq[a:b] is fine.
These generate combinatorial results lazily. permutations(it, r) —
ordered arrangements (order matters). combinations(it, r) — unordered
selections (order doesn't, no repeats). product(*its) — the Cartesian product
(nested loops), with repeat=n for self-products.
from itertools import permutations, combinations, product
list(permutations([1, 2, 3], 2)) # (1,2)(1,3)(2,1)(2,3)(3,1)(3,2)
list(combinations([1, 2, 3], 2)) # (1,2)(1,3)(2,3)
list(product([0, 1], repeat=2)) # (0,0)(0,1)(1,0)(1,1)
Counts grow fast (factorial / exponential), so keep r and inputs small or consume
lazily. product(a, b) replaces a nested for over two sequences.
groupby groups only consecutive items that share a key — it does not sort
first. So the same key appearing in non-adjacent positions creates multiple
groups. To get one group per key, sort by the same key function first.
from itertools import groupby
data = ["apple", "avocado", "banana", "apricot"]
# WRONG — not sorted: 'a' group splits because 'banana' is between
for k, g in groupby(data, key=lambda s: s[0]):
print(k, list(g)) # a [...] , b [...] , a [apricot]
data.sort(key=lambda s: s[0]) # sort by the SAME key
for k, g in groupby(data, key=lambda s: s[0]):
print(k, list(g)) # a [...], b [...] — correct
Also note each group is a lazy sub-iterator that's invalidated when you advance
to the next group — materialize it with list() if you need it later. Always sort
by the grouping key before groupby.
accumulate(iterable, func=operator.add) yields running totals — each output is
the function applied cumulatively, so by default you get a running sum. Pass a
different binary func for running max, product, etc.
from itertools import accumulate
import operator
list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10] — running sum
list(accumulate([1, 2, 3, 4], operator.mul)) # [1, 2, 6, 24] — running product
list(accumulate([3, 1, 4, 1, 5], max)) # [3, 3, 4, 4, 5] — running max
Unlike functools.reduce, which returns only the final value, accumulate
yields every intermediate result lazily. Use it for prefix sums and similar
scans.
Every itertools function returns a lazy iterator that computes items on
demand, so it never holds the whole sequence in memory. This lets you process
huge or infinite streams in constant memory, and chain operations into a
pipeline that only does the work actually consumed.
from itertools import count, islice
# find the first 5 squares over 1000 — from an infinite source
squares = (n * n for n in count(1))
big = (s for s in squares if s > 1000)
print(list(islice(big, 5))) # computed lazily, nothing materialized
sum(islice(count(1), 1_000_000)) # no million-element list built
The trade-off is that iterators are single-pass and not indexable. Reach for
itertools when streaming or composing transformations over large data;
materialize to a list only when you need random access or multiple passes.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.