In [x for row in rows if row for x in row], what does the if clause filter?
answer
- Write the clauses out as statements
- The if belongs to the clause left of it
- Guard the outer loop or the item
- Placement decides how much work is skipped
- Names must be bound further left
basics
~20 sThe rows, not the items. An if clause attaches to the for clause immediately to its left and runs at that loop level, so this one skips empty rows before the inner loop ever starts.
solid answer
~50 sAn `if` clause belongs to the `for` clause immediately preceding it, and it runs inside that loop, before the clauses to its right. In `[x for row in rows if row for x in row]` the test guards the *outer* loop: falsy rows are skipped entirely and the inner loop never runs for them. Move the same idea to the end — `[x for row in rows for x in row if x > 2]` — and it guards the *inner* loop, testing each individual item. So placement changes both **what is tested** and **how much work is done**: an outer-level guard can skip whole sublists, and it is also the only place a test can sit if it must run before the inner iterable expression is evaluated. A test may only use names bound by clauses to its left, so a per-item predicate cannot be placed on the outer clause — that raises `UnboundLocalError`, a subclass of `NameError`.
code
pycon · 5 lines>>> rows = [[1, 2], [], [3, 4]]
>>> [x for row in rows if len(row) > 1 for x in row]
[1, 2, 3, 4]
>>> [x for row in rows for x in row if x > 2]
[3, 4]go deeper
Know that a trailing if filters the values you are collecting, and that the test can only use names introduced earlier in the comprehension. Being able to read the two-clause form aloud is enough at this level.
Explain the mechanics: an if attaches to the for clause on its left and runs at that loop depth, so an outer-level guard skips whole sublists while a trailing one tests individual items. Show both rewritten as nested statements.
Show the operational angle: place the guard before the clause whose iterable is expensive or may fail, so the work is never started, and name the point at which the expression should become a named generator function instead.
Own the readability standard for the codebase — how many clauses a comprehension may carry before it becomes a named function — and make sure filtering rules that encode real domain constraints live somewhere testable rather than buried mid-expression.
## Where an if attaches A comprehension is a sequence of clauses: the first is always a `for`, and each later clause is either another `for` or an `if`. The transliteration rule is the same one that governs nesting — read left to right and write each clause inside the previous one, with `if` becoming a plain `if` statement. So: ```python result = [x for row in rows if row for x in row] ``` becomes: ```python result = [] for row in rows: if row: # runs once per row for x in row: result.append(x) ``` and moving the test to the end: ```python result = [x for row in rows for x in row if x > 2] ``` becomes: ```python result = [] for row in rows: for x in row: if x > 2: # runs once per item result.append(x) ``` The `if` does **not** filter the finished list, and it does **not** always apply to the output expression. It applies at whatever loop depth it is written at. ## What placement changes Three things at once, and it is worth separating them in an interview answer. **Which values are tested.** An outer-level test sees the sublist; an inner-level test sees the item. These are different objects, so the predicate itself is usually different code, not the same code moved. **How much work happens.** A guard on the outer clause can eliminate a whole sublist and everything the inner loop would have done with it. If the inner iterable is produced by a call, an outer guard also prevents that call from happening for skipped rows — which matters when the call is expensive, opens a resource, or can raise. **What is even legal.** A clause may only use names bound by clauses to its left. Writing `[x for row in rows if x > 2 for x in row]` fails, because `x` is bound by a clause further right; on CPython 3.14 the error is an `UnboundLocalError`, which is a subclass of `NameError`. The correct reading order is therefore also the binding order. ## Multiple if clauses Any number of `if` clauses may appear, at any depth, and consecutive ones at the same depth read as a chain of nested tests — equivalent to combining them with `and`, evaluated left to right with short-circuiting. Two separate clauses are often clearer than one long boolean expression, especially when the second test is only safe once the first has passed: ```python [x for row in rows if row if len(row) < 100 for x in row] ``` ## Ordering a guard before a dependent iterable The strongest practical use of an outer-level `if` is as a guard for the clause to its right. Because clauses are evaluated in written order, a test placed before a `for` clause runs before that clause's iterable expression is evaluated at all. That gives comprehensions the same short-circuit shape as a guarded nested loop: check the sublist is present and well-formed, then iterate it. ## The readability ceiling Once a comprehension carries two `for` clauses and two `if` clauses, the one-line form has stopped paying for itself. The transliteration is still mechanical, but a reader now has to perform it mentally to answer a simple question such as which values are being tested. The honest senior answer names a threshold — commonly one `for` plus one `if`, or two `for` clauses with a single guard — and moves anything past it into a named generator function or an explicit loop, where each level of nesting has its own line and can carry a comment. ## What interviewers are probing This question is not trivia about syntax placement. It checks whether the candidate has the transliteration rule available, because everything else — which values a test sees, what work is skipped, why one arrangement fails to run — falls straight out of that rule. A candidate who answers by rewriting the comprehension as nested statements has demonstrated the skill; a candidate who has merely memorised that the filter goes at the end has not, and will place a per-row guard in the wrong position the first time a sublist can be empty.
- Can an if clause reference a name bound by a for clause to its right?No. Clauses are evaluated left to right, and each binds names only for the clauses after it, so a test can only use names already bound to its left. `[x for row in rows if x > 2 for x in row]` raises `UnboundLocalError`, a subclass of `NameError`, because `x` is a comprehension-local name that no clause has bound yet at that point. It is a useful cross-check that you have the reading order right.
- What do two consecutive if clauses at the same level mean?They nest, which makes them equivalent to combining the predicates with `and`: the first is evaluated, and the second only if the first passed. Splitting them is worth doing when the second test is only meaningful or safe once the first has succeeded, or simply when two short conditions read better than one long boolean expression.
- How do you decide when a filtered nested comprehension should become a loop?When a reader can no longer answer which values a predicate sees without mentally rewriting the expression. In practice that is around two `for` clauses plus a single guard. Past it, move to a named generator function or an explicit nested loop, where each level gets its own line, can carry a comment, and can be stepped through in a debugger.
saying these in an interview costs you the question
- Says every if clause filters the final result
- Believes an if clause must come last
- Calls the placement purely cosmetic
- Puts a per-item predicate before the inner for clause
- Thinks an if can see names bound to its right
- Cannot say which values an outer-level test receives