How does itertools.product with repeat= replace nested for loops in Python?
answer
- Cartesian product, one tuple per pick
- Rightmost position advances fastest
- Nesting depth becomes a runtime value
- Inputs are materialized at construction
- n**k tuples, exponential in the repeat
basics
~20 sitertools.product yields the Cartesian product of its input iterables as tuples, exactly as nested for loops would, with the rightmost position varying fastest. repeat=n means 'use these same iterables n times', so product(x, repeat=3) equals three loops over x.
solid answer
~40 s`itertools.product(a, b)` yields every `(i, j)` pair, in the same order two nested `for` loops would produce them — the rightmost position advances fastest. `repeat=` is shorthand for repeating the argument list: `product([0, 1], repeat=3)` is the same as `product([0, 1], [0, 1], [0, 1])` and replaces three identical nested loops with one flat iteration whose depth is now a runtime value rather than hard-coded indentation. That is the real win: the nesting depth becomes a parameter. The output is lazy, one tuple at a time, but the **inputs are not** — `product` materializes each input iterable into a tuple at construction, so a generator handed to it is fully drained before the first result appears. Any empty input makes the whole product empty, and `repeat=0` yields exactly one empty tuple.
code
python · 4 linesfrom itertools import product
for flags in product([False, True], repeat=3):
print(flags)go deeper
Be able to say that itertools.product yields every combination of one element from each input, and that repeat=3 over one list is the same as three nested loops.
Explain the ordering (rightmost varies fastest), that repeat= makes the nesting depth a runtime parameter, and that inputs are materialized while output stays lazy.
Show that you size the space with math.prod or n**k first, and can argue when explicit nested loops win because they let you prune whole subtrees.
Own the tradeoff between a flat, parameterized enumeration that is easy to reason about and a hand-rolled loop nest that can cut the space early; say which one the team should default to and why.
### What the Cartesian product is The Cartesian product of several iterables is every way of picking one element from each, in order. `itertools.product` yields those picks as tuples. With two arguments it is a pair of nested loops; with k arguments it is k nested loops; and the ordering matches the loops exactly, with the last position cycling fastest and the first position changing most slowly. ```python from itertools import product # these two loops produce identical sequences for a in 'xy': for b in [0, 1]: print((a, b)) for pair in product('xy', [0, 1]): print(pair) ``` ### What repeat= buys you `repeat=n` repeats the whole argument list n times before taking the product. `product([0, 1], repeat=3)` is literally `product([0, 1], [0, 1], [0, 1])`, and it enumerates all eight three-bit patterns. `product(a, b, repeat=2)` is `product(a, b, a, b)`. The reason this matters is not brevity but **parameterization**. Nested `for` loops encode their depth in the source text: three levels of indentation can only ever enumerate triples. With `repeat=k`, the depth is an ordinary variable, so the same line of code enumerates pairs, triples or ten-tuples depending on a value computed at runtime. Any time you catch yourself writing a recursive helper whose only job is to build fixed-width tuples from one alphabet, `product(..., repeat=k)` is the flat replacement. It also flattens the code: the loop body sits at one indentation level instead of k, and `break`/`continue` behave straightforwardly against a single loop rather than needing flags or a labelled-loop workaround Python does not have. ### Laziness of the output, eagerness of the input `product` returns an iterator and produces one tuple at a time, so the memory it holds is the input tuples plus the current output tuple. But the **input side is eager**: at construction, each input iterable is converted to a tuple immediately, because the algorithm has to revisit every input element many times over. Two consequences follow. First, a generator passed to `product` is exhausted before a single result is yielded: ```python gen = (x for x in range(3)) p = product(gen, repeat=2) list(gen) # [] — product already drained it len(list(p)) # 9 — the results are all still there ``` Second, an endless iterator cannot be an input: `product` would try to materialize it and never return. Bound such an iterator before passing it in. ### Counts and edge cases The number of tuples is the product of the input lengths — `math.prod([len(a), len(b), len(c)])` — and with `repeat=k` over one iterable of length n it is exactly n**k. That is exponential in the repeat count, which is the thing to compute before iterating: a ten-value list with `repeat=8` is 100 million tuples, no bigger in memory than one tuple but hours of work. Two edge cases interviewers like: * If **any** input is empty, the product is empty — there is no way to pick one element from an empty set, so zero tuples come out. * `product(anything, repeat=0)` yields exactly one item, the empty tuple `()`. That is the mathematically correct empty product, not a bug, and it mirrors `math.prod([]) == 1`. ### product versus its combinatorial neighbours `product` is the one that lets a value repeat *within* a tuple and treats position as meaningful: `product('ab', repeat=2)` gives all four of `('a','a')`, `('a','b')`, `('b','a')`, `('b','b')`. `itertools.permutations` drops the repeats (`('a','a')` cannot occur), `itertools.combinations` drops repeats and orderings, and `itertools.combinations_with_replacement` keeps repeats but drops orderings. Choosing between them is choosing two independent yes/no answers: may an element repeat, and does order matter? ### Where it shows up in real code Grid enumeration is the archetype: every combination of settings in a configuration sweep, every cell of a two-dimensional board, every truth assignment over a set of boolean flags, every pairing of a currency with a region. The mechanical tell in review is a stack of loops all iterating structurally-similar sequences whose only purpose is to assemble a tuple — that stack is a `product` call. The counter-indication is real too. If most of the space is going to be rejected by a condition checked at the first or second level, a hand-written nested loop can skip whole subtrees, while `product` faithfully generates each doomed tuple before your filter sees it. When the pruning matters, split the enumeration or filter the input lists before taking the product.
- What does itertools.product(['a', 'b'], [], ['c']) yield?Nothing at all — the iterator is immediately exhausted. A Cartesian product requires picking one element from every input, and one of them has no elements to pick, so the result is empty. This is a common silent bug: a single accidentally-empty configuration list makes an entire sweep run zero iterations while raising no error.
- How many tuples does itertools.product(range(10), repeat=8) produce, and does its laziness help?10**8, a hundred million tuples. Laziness keeps memory flat — one tuple at a time — but does nothing for runtime, so the loop still executes a hundred million bodies. Compute the size first with `math.prod` or `n ** k`, and if it is too large, reduce the alphabet or the repeat count rather than hoping the iterator saves you.
- When is a hand-written stack of nested loops still the better choice over itertools.product?When you can prune. Nested loops let you `continue` or `break` at an outer level and skip an entire subtree of the space; `product` generates every tuple faithfully and any filter runs after the fact. If an outer-level condition rejects most of the space, keep the explicit loops, or filter the input lists before handing them to `product`.
saying these in an interview costs you the question
- Says product yields tuples in an unspecified order
- Thinks the leftmost position varies fastest
- Believes product consumes its input lazily as it goes
- Claims repeat= repeats each element rather than the argument list
- Assumes laziness makes an exponential sweep affordable
- Confuses product with permutations of the same iterable