How do `[f(x) if c(x) else g(x) for x in xs]` and `[f(x) for x in xs if c(x)]` differ?
answer
- Position decides, not the keyword
- Before the for versus after the for
- One shortens the output, one does not
- The filter clause takes no else
- Filter runs before the element expression
basics
~20 sThe first maps every element, choosing per element between f and g, so the output has one item per input. The second filters: elements failing c are dropped, so the output is shorter. Position decides which you get.
solid answer
~50 sPosition, not keyword, decides the meaning. A conditional expression lives in the **element slot**, before the `for`, and must have an `else`; it transforms every element, so the result always has the same length as the input. A bare `if` **after** the `for` clause is a filter, takes no `else`, and shortens the result. Writing `[f(x) for x in xs if c(x) else g(x)]` is a `SyntaxError` — the filter clause has no else branch. You can combine them: `[f(x) if c(x) else g(x) for x in xs if keep(x)]` filters first, then maps the survivors. Evaluation order matters for cost and safety: the filter runs before the element expression, so `[100 / n for n in ns if n]` never divides by zero, while putting the same test in the element slot would still need a fallback value.
code
python · 5 linesxs = [1, 2, 3, 4]
mapped = [x * 10 if x % 2 else x for x in xs]
filtered = [x * 10 for x in xs if x % 2]
combined = [x * 10 if x > 2 else x for x in xs if x % 2]
print(mapped, filtered, combined)go deeper
Learn the two positions by heart: if/else before the for picks a value for every item, a bare if after the for drops items. Predict the output length before you run the line.
Explain why the filter clause cannot take an else and why the element expression must, and show the combined form. Know that the filter is evaluated first, so it can guard a risky element expression.
Use the ordering deliberately — cheap selective test in the filter, expensive work in the element expression — and spot the duplicate-evaluation smell when the same predicate appears in both slots. Know when a comprehension has grown past readability into a plain loop.
Set the boundary for the codebase: how many clauses a comprehension may carry before it becomes a loop, and when a bounded list is acceptable versus a lazy form, so that stream-shaped inputs never get square brackets around them.
### Two different `if`s in one line A comprehension has three kinds of parts: an **element expression**, one or more `for` clauses, and optional `if` filter clauses. The word `if` appears in two completely different roles, and confusing them is one of the most common comprehension mistakes. ```python [f(x) if c(x) else g(x) for x in xs] # element expression is a conditional expression [f(x) for x in xs if c(x)] # filter clause ``` The first line's `if` belongs to the element expression, which just happens to be a conditional expression; it is not a comprehension feature at all. The second line's `if` is comprehension syntax. Read the position, not the keyword: **before the `for` it chooses a value, after the `for` it chooses whether there is a value.** ### Consequences for the output The conditional expression is a map. Every input contributes exactly one output, so `len(result) == len(xs)`. The filter is a selection: `len(result) <= len(xs)`, and the survivors are unchanged in relative order. Ask which one you want by asking what the output length should be — that question resolves the choice immediately. ### Why the else is mandatory in one and forbidden in the other An element expression must produce a value for every element it is evaluated on, so its conditional expression needs both branches — a one-armed form does not exist. A filter clause is a test, so it has no value to supply and no `else` to attach; `[x for x in xs if c(x) else 0]` is rejected at compile time. If you want to express "drop it" and "transform it" in one line, you write two separate parts, not one hybrid. ### Evaluation order A comprehension unrolls to a loop, and the order is: iterate the outermost `for`, evaluate each filter in written order (skipping the item on the first falsy one), then evaluate the element expression for the items that survive. Three practical consequences follow. **Guarding.** Because the filter runs first, it can protect the element expression. In a log-ingest pipeline, `[record.split("|", 1)[1] for record in batch if "|" in record]` never indexes past the end. The equivalent written as a conditional expression in the element slot still has to invent a value for the bad records — often `None`, which then leaks downstream. **Cost.** The filter is evaluated for every input, the element expression only for the survivors. Put the cheap, highly selective test in the filter; keep the expensive work in the element expression. If a test is expensive and you need its value too, a conditional expression cannot help you avoid evaluating it twice — that is where a nested comprehension over an intermediate sequence, or a plain loop, reads better. **Duplicate work.** `[f(x) if c(x) else g(x) for x in xs if c(x)]` calls `c` twice per surviving element. If `c` is pure and cheap that is merely noise; if it is neither, restructure. ### Memory, not just shape The same clause rules apply to set and dict comprehensions and to generator expressions, and the choice of brackets is a memory decision independent of the map/filter one. In a log-ingest pipeline that reads a continuous stream, a list comprehension over the stream materializes every element and is a straightforward route to unbounded memory growth; the generator-expression form with round brackets produces items one at a time. Sizing rule of thumb from that setting: if you cannot state a bound for the input length, do not put square brackets around it. ### Readability limits A comprehension carrying both a conditional expression and a filter has four moving parts on one line — two tests and two result expressions — and it is at the edge of what reads well. Once a second filter or a second `for` clause joins them, unroll it into a `for` loop with an `if` statement; the loop version also lets each branch do more than produce a value, such as counting how many records took each path. ### A note on how it compiles Since Python 3.12 (PEP 709), list, dict and set comprehensions are inlined into the enclosing function rather than running in their own implicit function object, which makes them noticeably faster and removes a stack frame from tracebacks. Generator expressions are unaffected. This changes performance and traceback shape, not the map-versus-filter semantics described above, which have been the same for the life of comprehensions.
- In `[f(x) for x in xs if c(x)]`, which is evaluated first for a given element, `c(x)` or `f(x)`?`c(x)`. The comprehension unrolls to a loop whose body tests each filter clause first and only then evaluates the element expression, so `f` is called just for the survivors. That ordering is what lets a filter guard a risky element expression, such as skipping records that lack a separator before splitting on it.
- Can you combine a conditional expression and a filter in one comprehension?Yes: `[f(x) if c(x) else g(x) for x in xs if keep(x)]`. The filter runs first and drops elements, then the element expression maps each survivor. It is legal and occasionally the clearest form, but with two tests and two results on one line it is close to the point where a plain `for` loop reads better.
- Does the same rule hold for dict and set comprehensions and generator expressions?Yes. All comprehension forms share the clause grammar: the element expression comes first and may be a conditional expression, filter clauses follow the `for` and take no `else`. The bracket choice is an orthogonal decision about whether the whole result is materialized or produced lazily one item at a time.
The element expression is a sorting hat that assigns every arrival to a house; the filter clause is the door policy that decides who gets in at all.
saying these in an interview costs you the question
- Puts the else after the for clause
- Thinks the trailing if can take an else
- Expects the filtered result to keep its original length
- Believes the element expression runs before the filter
- Calls an expensive test twice in one comprehension