Skip to content

List, Dict & Set Comprehensions Interview Questions & Answers

15 questions Updated 2026-06-18 Share:

Python interview questions on list, dict, and set comprehensions, filtering with if, conditional expressions, nested comprehensions, and when not to use them.

Read the in-depth guidePython Comprehensions Explained — List, Dict, Set, and When Not to Use Them(opens in new tab)
15 of 15

A list comprehension is a concise expression that builds a list in a single readable line: [expression for item in iterable]. It replaces the common pattern of creating an empty list and append-ing in a for loop.

# the verbose way
squares = []
for n in range(5):
    squares.append(n * n)

# the comprehension
squares = [n * n for n in range(5)]   # [0, 1, 4, 9, 16]

Beyond brevity, comprehensions are usually faster than an equivalent loop (the iteration runs in optimized C) and they signal intent: "I'm transforming a sequence into a new list." Reach for one whenever you're building a list by mapping or filtering another iterable.

A trailing if clause filters items — only elements for which it is truthy are kept. A conditional expression (x if cond else y) goes at the front, before the for, because it's part of the output expression, not a filter.

nums = range(6)

evens = [n for n in nums if n % 2 == 0]          # filter: [0, 2, 4]
labels = ["even" if n % 2 == 0 else "odd"        # transform every item
          for n in nums]                         # ['even','odd',...]
both = [n * 2 for n in nums if n > 2]            # filter THEN transform

Remember the position rule: a filter if comes after the for; a if/else expression comes before it. Mixing them up is a common beginner error.

You can chain multiple for clauses to flatten or iterate over nested data. The clauses read left to right in the same order as nested loops. You can also nest a comprehension inside the output expression to build a list of lists.

matrix = [[1, 2], [3, 4]]

flat = [x for row in matrix for x in row]   # [1, 2, 3, 4]
# equivalent to:
#   for row in matrix:
#       for x in row:

grid = [[r * c for c in range(3)] for r in range(3)]  # list of rows

The trap is reading the order backwards — the outer loop is written first. Keep nesting shallow; two levels is usually the readability limit before a plain loop is clearer.

The same syntax works for dicts and sets. A dict comprehension uses {key: value for ...} and a set comprehension uses {expr for ...} (no colon). Sets automatically deduplicate results.

names = ["ada", "grace", "ada"]

lengths = {n: len(n) for n in names}     # {'ada': 3, 'grace': 5}
unique  = {n for n in names}             # {'ada', 'grace'}  (deduped)
swapped = {v: k for k, v in lengths.items()}  # invert a dict

Note {} alone is an empty dict, not a set — use set() for an empty set. Dict and set comprehensions accept the same if filters and nested for clauses as list comprehensions.

Comprehensions are for building a collection. Avoid them when you only want side effects (printing, writing to a DB) — that abuse hides intent and builds a throwaway list. Use a plain for loop instead. Also skip them when the logic is so complex that the line becomes unreadable.

# bad: comprehension purely for side effects
[print(x) for x in items]      # builds a useless list of Nones

# good: a loop says "do this for each"
for x in items:
    print(x)

Comprehensions overlap with map/filter, but are usually more readable and avoid lambda. Prefer a generator expression (...) over a list comp when you only iterate once and don't need to materialize the whole result.

A generator expression uses parentheses (...) and is lazy — it yields items one at a time instead of building the whole list in memory. Ideal for large or infinite data and for feeding aggregate functions.

squares_list = [n * n for n in range(1000000)]   # builds a big list
squares_gen  = (n * n for n in range(1000000))   # lazy, tiny memory

total = sum(n * n for n in range(1000000))       # no intermediate list
next(squares_gen)                                 # 0 — pull one at a time

Rule of thumb: use a genexp when you only iterate once or pass it to sum/max/ any; use a list comp when you need indexing, length, or to reuse the result.

No — in Python 3 a comprehension has its own scope, so its loop variable doesn't exist afterward and won't clobber an outer variable of the same name. (This was not true for list comps in Python 2.)

x = "important"
result = [x for x in range(3)]
print(x)        # 'important' — outer x untouched

[i for i in range(3)]
print(i)        # NameError — i never leaked

Rule of thumb: comprehension variables are private to the comprehension; you can safely reuse names without side effects.

The walrus := lets you compute a value once, bind it, and reuse it in both the filter and the output — avoiding a double computation of an expensive call.

data = [" 12 ", "x", " 7 "]

# without walrus: parse twice or use a helper
result = [v for s in data if (v := s.strip()).isdigit()]
# 'v' is reused: filtered on isdigit AND used as the output
result        # ['12', '7']

Rule of thumb: use := in a comprehension when you'd otherwise call the same expensive function in both the if and the output expression.

Dict comprehensions take the same if filter (after the for) and conditional expressions (in the key or value). This makes inverting, filtering, or remapping a dict a one-liner.

prices = {"a": 10, "b": 0, "c": 5}

in_stock = {k: v for k, v in prices.items() if v > 0}   # drop zero
capped   = {k: min(v, 8) for k, v in prices.items()}    # transform values
flagged  = {k: ("free" if v == 0 else v) for k, v in prices.items()}

Rule of thumb: filter with a trailing if, transform with expressions in the key/ value slots — same rules as list comprehensions.

A comprehension runs its iteration and append logic in optimized C with a specialized bytecode, avoiding the repeated list.append attribute lookup and method call a manual loop performs each iteration.

# slower: attribute lookup + bound-method call every iteration
out = []
append = out.append          # caching append helps, but still Python-level
for n in range(1000):
    append(n * n)

# faster: dedicated LIST_APPEND bytecode, no per-item method lookup
out = [n * n for n in range(1000)]

Rule of thumb: comprehensions win on speed and clarity for building collections; for side effects or complex bodies, a plain loop is the right call.

Multiple for clauses flatten (one combined output list); a nested comprehension in the output position produces a list of lists. The placement of brackets decides the shape.

m = [[1, 2], [3, 4]]

flat   = [x for row in m for x in row]        # [1, 2, 3, 4]
nested = [[x * 10 for x in row] for row in m]  # [[10, 20], [30, 40]]

Rule of thumb: chained fors flatten; an inner [...] in the expression slot preserves structure. Read chained fors top-down like nested loops.

Yes — each for clause can reference variables bound by the clauses to its left, exactly like nested loops. This lets you generate dependent combinations.

# upper triangle of pairs — j depends on i
pairs = [(i, j) for i in range(3) for j in range(i + 1, 3)]
# [(0, 1), (0, 2), (1, 2)]

# filter can also use earlier variables
[(i, j) for i in range(3) for j in range(3) if i != j]

Rule of thumb: inner clauses see outer ones (left-to-right), so you can build triangular ranges, dependent filters, and Cartesian subsets in one expression.

Without the walrus operator, factor the work into a generator step or helper so the expensive call runs once per item, then filter/transform its result. This keeps both correctness and readability.

# naive: process(x) called twice per item
result = [process(x) for x in data if process(x) is not None]

# better: compute once via an inner generator
processed = (process(x) for x in data)
result = [p for p in processed if p is not None]

Rule of thumb: never repeat an expensive call in both the filter and output — use an intermediate generator (or :=) to compute it a single time.

It returns an empty collection of the matching type — never an error. An empty iterable simply yields no items, so you get [], {}, or set().

[x * 2 for x in []]            # []
{k: v for k, v in []}         # {}
{x for x in ()}               # set()
[x for x in range(10) if x > 100]   # [] — filter removes everything

Rule of thumb: comprehensions degrade gracefully to empty results on empty/filtered input — no need to guard against empty sources.

When it has multiple for clauses plus filters plus a conditional expression all on one line, readability collapses. Break it into a loop or a helper function — the goal is clarity, not minimal line count.

# too dense:
r = [f(x) if g(x) else h(x) for sub in data for x in sub if x and p(x)]

# clearer:
r = []
for sub in data:
    for x in sub:
        if x and p(x):
            r.append(f(x) if g(x) else h(x))

Rule of thumb: if you can't read the comprehension aloud in one breath, expand it to a loop — comprehensions should simplify, not obfuscate.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel