In the comprehension [x for row in rows for x in row], which for clause is the outer loop?
answer
- Rewrite it as nested statements
- Clauses read left to right
- Leftmost clause is the outer loop
- Output expression runs innermost
- One level of flattening only
basics
~20 sThe leftmost one. Clauses read left to right in the same order you would write nested for statements, so for row in rows is the outer loop and for x in row the inner one. The result is one flat list.
solid answer
~50 sMultiple `for` clauses in a comprehension are a direct transliteration of nested `for` statements written outermost first, so the clauses are read **left to right**: `for row in rows` is the outer loop, `for x in row` is the inner loop, and the output expression `x` runs in the body of the innermost loop. That is why this exact form is the idiomatic one-expression flatten of a list of lists. It flattens **exactly one level** — each additional level of nesting needs another `for` clause, and truly arbitrary depth needs a recursive generator function instead. Writing the clauses in the opposite order fails with a `NameError`, because a clause can only use names bound by clauses to its left. Two clauses is about the readability limit; past that, a plain nested loop or a helper function reads better.
code
pycon · 10 lines>>> rows = [[1, 2], [3], [4, 5, 6]]
>>> [x for row in rows for x in row]
[1, 2, 3, 4, 5, 6]
>>> flat = []
>>> for row in rows:
... for x in row:
... flat.append(x)
...
>>> flat
[1, 2, 3, 4, 5, 6]go deeper
Be ready to read [x for row in rows for x in row] aloud as two nested loops and say what it returns for a small ragged input. Knowing that the leftmost clause is the outer loop is the whole ask here.
Explain the mechanics: each clause becomes a loop nested in the previous one, the output expression runs in the innermost body, and a clause may only use names bound to its left. Show the NameError from reversing the clauses.
Demonstrate judgment about when to stop: two clauses is the readability ceiling, unknown depth needs a recursive generator, and strings and bytes flatten into characters and integers respectively. Name the quadratic flattening anti-patterns rather than merely avoiding them.
Own the convention: decide where the team draws the line between a flattening expression and a named helper, and how flattening interacts with data whose shape is not guaranteed, so ragged or unexpectedly deep input fails loudly instead of silently producing letters.
## The rule: left to right, outermost first A comprehension with more than one `for` clause is defined as a transliteration of nested statements. Read the clauses left to right and write each one as a loop nested inside the previous one; the output expression, which sits at the far *left* of the comprehension, is what executes in the body of the *innermost* loop. So this comprehension: ```python flat = [x for row in rows for x in row] ``` is exactly this statement form: ```python flat = [] for row in rows: # first clause -> outer loop for x in row: # second clause -> inner loop flat.append(x) # output expression -> innermost body ``` The only thing that moves is the output expression, which is written first but evaluated last. Everything else keeps its order. Once you internalise that single rewrite, every multi-clause comprehension becomes readable mechanically rather than by intuition. ## Why this is the flattening idiom Because the outer loop walks the sublists and the inner loop walks their items, appending each item to one result, the expression turns a list of lists into a single flat list. It is the standard one-expression flatten, and it has some useful properties: * **Ragged input is fine.** The sublists need not be the same length, and an empty sublist simply contributes nothing. * **The sublists need not be lists.** Any iterable works — tuples, sets, ranges, generators — because the inner clause only requires something iterable. * **The outer object need not be a list** either; it just has to be iterable. ## It flattens exactly one level This is the misconception that shows up most often. `[x for row in rows for x in row]` removes **one** level of nesting. Given `[[1, 2], [3]]` you get `[1, 2, 3]`; given `[[[1, 2]], [[3]]]` you get `[[1, 2], [3]]` — still nested. Each extra level costs another `for` clause, and by three clauses the expression is usually harder to read than the loop it replaces. For a structure of unknown or mixed depth, a comprehension is the wrong tool: write a small recursive generator function that yields leaves and recurses into anything iterable, with an explicit stopping rule for strings so they are not exploded into characters. That string case is worth its own warning. A `str` is iterable, and iterating it yields one-character strings, so flattening a list of words gives you a list of letters. A `bytes` object is iterable too, and iterating it yields **integers**, not one-byte objects — so the same code over decoded text and over raw bytes produces two very different results. ## The reversed-order bug A clause may only reference names bound by clauses to its left, because those clauses are the enclosing loops. Writing `[x for x in row for row in rows]` raises `NameError: name 'row' is not defined`: the first clause tries to iterate `row`, which nothing has bound yet. The error is loud and immediate, which is a small mercy — but the fix is not to shuffle the clauses until it runs, it is to write the nested statements out and translate them back. ## Alternatives and what they cost * `list(itertools.chain.from_iterable(rows))` produces the same flat list, consuming the sublists one at a time. * An explicit loop calling `list.extend` per sublist is equally clear and often the most readable option when there is any surrounding logic. * `sum(rows, [])` also flattens, and should be avoided: it builds a new intermediate list on every step, so it is quadratic in the total number of items. `functools.reduce` with list concatenation has the same defect. ## Same syntax, other brackets The identical clause ordering applies to the dict, set and generator forms — `{x for row in rows for x in row}` deduplicates while flattening, and swapping the brackets for parentheses gives the generator form of the same nesting. The nesting rule does not change with the bracket; only what is built from the innermost body does. ## Interview framing Interviewers ask this because it separates people who have memorised the flatten one-liner from people who can derive it. The strong answer is the rewrite: state that the clauses map onto nested `for` statements in written order, produce the four-line loop version, then note the one-level limit and the readability ceiling at two clauses.
- What happens if you write the two for clauses in the opposite order?`[x for x in row for row in rows]` raises `NameError: name 'row' is not defined`. Each clause is an enclosing loop for the clauses to its right, so a clause can only use names bound to its left. Here the first clause tries to iterate `row` before anything has bound it. The failure is immediate rather than a wrong answer, which makes it easy to spot.
- How would you flatten a structure whose nesting depth is not known in advance?Not with a comprehension — each `for` clause removes exactly one fixed level. Write a small recursive generator function that iterates the object, yields items that are not iterable, and recurses into the ones that are, with an explicit guard so a `str` or `bytes` is treated as a leaf rather than exploded into characters or integers.
- Why is sum(rows, []) a poor way to flatten a list of lists?It concatenates left to right, allocating and copying a new list at every step, so the total work is quadratic in the number of items rather than linear. `[x for row in rows for x in row]` or `list(itertools.chain.from_iterable(rows))` each build the result once. On small inputs the difference is invisible, which is exactly why it survives into code that later grows.
The clauses are an indentation ladder written on one line: the leftmost clause is the least-indented loop, and the output expression is the deepest line in the body.
saying these in an interview costs you the question
- Says the rightmost for clause is the outer loop
- Claims one comprehension flattens to arbitrary depth
- Reads multi-clause comprehensions right to left
- Thinks flattening a list of words yields whole words
- Recommends sum(rows, []) as a linear flatten
- Cannot rewrite the comprehension as nested for statements