Learn What List Comprehensions Are in Python: Absolute Beginners Tutorial

List comprehensions are one of Python's most recognizable features. They let you build a new list from an existing sequence in a single readable line, replacing the pattern of creating an empty list and filling it with a loop. This tutorial covers the syntax, how to add conditions, common use cases, and where comprehensions should and should not be used — with working code examples throughout.

If you have written Python for loops that build lists, you have already done everything a list comprehension does — the syntax just packages it differently. This tutorial walks through what they are, how the syntax works, and where they make your code cleaner.

What Is a List Comprehension?

A list comprehension is a shorthand for building a new list. Instead of writing a loop that appends items one by one, you write a single expression inside square brackets that describes the list you want. The official Python documentation describes them as providing "a concise way to create lists." That brevity is the point: the same logic, fewer moving parts.

Here is the classic pattern you might already know — a loop that squares each number in a range:

python
squares = []
for n in range(5):
    squares.append(n ** 2)

print(squares)
# [0, 1, 4, 9, 16]

The list comprehension version produces exactly the same result:

python
squares = [n ** 2 for n in range(5)]

print(squares)
# [0, 1, 4, 9, 16]
Note

Both versions produce an identical list. The comprehension is not a different operation — it is a more compact way to write the same logic.

The Syntax Explained

A list comprehension has three parts, always written inside square brackets:

python
[expression   for item in iterable]
#  ^^^^^^^^^       ^^^^    ^^^^^^^^
#  what goes        loop    what you
#  in the list    variable  loop over

Each part has a specific role:

Role
The value that gets added to the new list for each iteration. It can be the loop variable itself, a calculation, a method call, or any valid Python expression.
Example
n ** 2 — squares the loop variable before adding it to the list.
Role
The loop clause. item is any variable name you choose; iterable is any sequence Python can loop over — a list, range, string, tuple, or similar object.
Example
for n in range(5) — loops over the integers 0, 1, 2, 3, 4.
Role
An optional filter. If present, only items for which the condition evaluates to True are passed to the expression and included in the new list.
Example
if n % 2 == 0 — keeps only even numbers.
Syntax Anatomy click any part to explore it
[x ** 2 for x in range(5) if x % 2 == 0]
expression
loop variable
iterable
filter condition
click a colored part above
Each part of a list comprehension has a specific job. Click the expression, loop variable, iterable, or condition to learn what it does in this example.

Tip: the filter condition is optional — [x ** 2 for x in range(5)] is also valid.

Step Tracer watch the comprehension run item by item
Tracing: [x * 2 for x in [1, 2, 3, 4, 5] if x % 2 != 0]
x
x % 2 != 0
x * 2
1
True
2
2
False
3
True
6
4
False
5
True
10
result = [ ]
ready — press step to begin

Comprehension vs. For Loop

The table below shows the same operation written both ways. The output is always identical — the difference is only in how the code is structured.

For loop
result = []
for w in words:
  result.append(w.upper())
Comprehension
[w.upper() for w in words]
For loop
result = []
for n in nums:
  result.append(n * 2)
Comprehension
[n * 2 for n in nums]
For loop
result = []
for s in strings:
  result.append(len(s))
Comprehension
[len(s) for s in strings]
Check Your Understanding question 1 of 4

Adding Conditions to Filter Items

The optional if clause at the end of a comprehension acts as a filter. Items that do not satisfy the condition are skipped and never reach the expression.

python
# Keep only even numbers from 0 to 9
evens = [x for x in range(10) if x % 2 == 0]
print(evens)
# [0, 2, 4, 6, 8]

# Keep only words longer than 3 characters
words = ["hi", "hello", "bye", "python", "is"]
long_words = [w for w in words if len(w) > 3]
print(long_words)
# ['hello', 'python']
Pro Tip

The condition tests each item before the expression runs. If a condition would cause an error (such as calling a method on None), use a condition that checks for that first: [x.upper() for x in items if x is not None].

You can also combine a transformation and a filter in the same comprehension:

python
# Square only the even numbers
squared_evens = [x ** 2 for x in range(10) if x % 2 == 0]
print(squared_evens)
# [0, 4, 16, 36, 64]
Code Builder place the tokens in the correct order

Build a list comprehension that collects only the positive numbers from a list called nums.

your code will appear here...
[x for x in nums if x > 0] if x < 0] x ** 2
Hint: The structure is always [expression for item in iterable if condition]. Start with the expression inside the opening bracket, add the loop clause, then the condition with the closing bracket. The distractor if x < 0] filters for negative numbers, not positive ones.

How to Convert a For Loop

  1. Start with a working for loop

    Write out the standard pattern: an empty list, a loop, and an append call. Make sure it works correctly before converting it. Example: result = []; for item in collection: result.append(item.lower()).

  2. Identify the expression and the iterable

    Look at what the loop appends — that is your expression. Look at what the loop iterates over — that is your iterable. In the example above, the expression is item.lower() and the iterable is collection.

  3. Write the comprehension inside square brackets

    Open a square bracket, write the expression, then write for item in iterable, then close the bracket: [item.lower() for item in collection]. Assign it to a variable the same way you would the loop result.

  4. Add an optional condition to filter items

    If the loop had an if statement controlling whether to append, add that same condition at the end: [item.lower() for item in collection if item]. Only items that pass the condition are included.

Common Use Cases

The following patterns come up constantly in real Python code. Each one maps directly to the syntax you have already learned — the only thing that changes is what you put in the expression position and whether you add a filter.

Transforming items in a list

python
names = ["alice", "bob", "carol"]
upper_names = [name.capitalize() for name in names]
print(upper_names)
# ['Alice', 'Bob', 'Carol']

Filtering items from a list

python
scores = [45, 82, 91, 37, 68, 75]
passing = [s for s in scores if s >= 60]
print(passing)
# [82, 91, 68, 75]

Extracting values from a list of objects

python
users = [
    {"name": "Alice", "active": True},
    {"name": "Bob", "active": False},
    {"name": "Carol", "active": True},
]

active_names = [u["name"] for u in users if u["active"]]
print(active_names)
# ['Alice', 'Carol']

Working with strings and characters

python
# Collect only the vowels from a word
word = "comprehension"
vowels = [ch for ch in word if ch in "aeiou"]
print(vowels)
# ['o', 'e', 'e', 'i', 'o']

Calling your own function in the expression

Any valid Python expression works in the expression position — including a call to a function you wrote yourself. This is one of the most practical patterns in real code: define a small helper, then apply it across a list in one line.

python
def celsius_to_fahrenheit(c):
    return (c * 9 / 5) + 32

readings = [0, 20, 37, 100]
converted = [celsius_to_fahrenheit(c) for c in readings]
print(converted)
# [32.0, 68.0, 98.6, 212.0]

# Works with built-in functions too
raw = ["  hello  ", " world ", "  python"]
cleaned = [s.strip() for s in raw]
print(cleaned)
# ['hello', 'world', 'python']

Building a list of tuples or pairs

The expression position is not limited to single values. You can return a tuple from each iteration, which gives you a list of pairs — useful for building lookup tables, coordinate sets, or paired data without zip().

python
# Each iteration produces a (number, square) tuple
pairs = [(x, x ** 2) for x in range(1, 6)]
print(pairs)
# [(1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]

# Pair each word with its length
words = ["Python", "list", "comprehension"]
word_lengths = [(w, len(w)) for w in words]
print(word_lengths)
# [('Python', 6), ('list', 4), ('comprehension', 13)]
Note

When the expression is a tuple, the parentheses around it are optional inside a comprehension — [(x, x**2) for x in range(5)] and [x, x**2 for x in range(5)] are not the same thing. The second form is a SyntaxError. Always wrap tuple expressions in parentheses to make the intent clear.

What happens when the iterable is empty?

A comprehension over an empty list or range does not crash — it silently returns an empty list. This is the correct and expected behavior, and you can rely on it.

python
result = [x * 2 for x in []]
print(result)
# []

result = [x for x in range(5) if x > 100]
print(result)
# []  — all items filtered out, still no error
Spot the Bug click the line that contains the error

This code should produce a list of lengths for words longer than 4 characters. One line contains a mistake. Click the line you think is wrong, then check your answer.

words = ["sky", "python", "list", "comprehension", "bug"]
result = [len(w) for w in words if len(w) > 4]
print(results)
The bug is on line 3. The variable was assigned as result on line 2, but line 3 tries to print results with an extra s. Python would raise a NameError: name 'results' is not defined. The fix is to change print(results) to print(result).

Multiple Conditions in a List Comprehension

You can stack more than one condition in a comprehension by chaining if clauses. Python treats them as a logical and — an item must satisfy every condition to be included.

python
# Two conditions: divisible by 2 AND by 3
nums = range(1, 21)
result = [x for x in nums if x % 2 == 0 if x % 3 == 0]
print(result)
# [6, 12, 18]

# The same logic written with a combined condition using 'and'
result = [x for x in nums if x % 2 == 0 and x % 3 == 0]
print(result)
# [6, 12, 18]
Style Note

Two chained if clauses and a single if x % 2 == 0 and x % 3 == 0 produce identical results. Most Python style guides favor the single and form for clarity — it makes the logical relationship between conditions explicit.

You can also mix a ternary expression in the expression position with a filter condition at the end:

python
# Filter out zeroes, then label each remaining number
nums = [0, 3, 0, 7, -2, 5]
result = ["pos" if x > 0 else "neg" for x in nums if x != 0]
print(result)
# ['pos', 'pos', 'neg', 'pos']

Using enumerate() and zip() with List Comprehensions

Any iterable works as the source of a list comprehension — including the built-ins enumerate() and zip(), which let you work with index-value pairs or multiple sequences at once.

enumerate() — looping with an index

enumerate(iterable) yields (index, value) tuples. You can unpack both in the for clause:

python
fruits = ["apple", "banana", "cherry"]

# Pair each item with its position number
labeled = [f"{i}: {fruit}" for i, fruit in enumerate(fruits)]
print(labeled)
# ['0: apple', '1: banana', '2: cherry']

# Keep only items at even-numbered positions
even_pos = [fruit for i, fruit in enumerate(fruits) if i % 2 == 0]
print(even_pos)
# ['apple', 'cherry']

zip() — looping over two lists in parallel

zip(a, b) pairs items from two iterables position by position. Again, you can unpack both names in the for clause:

python
names = ["Alice", "Bob", "Carol"]
scores = [88, 74, 95]

# Combine into formatted strings
report = [f"{name}: {score}" for name, score in zip(names, scores)]
print(report)
# ['Alice: 88', 'Bob: 74', 'Carol: 95']

# Filter: only show names where the paired score passes
passing = [name for name, score in zip(names, scores) if score >= 80]
print(passing)
# ['Alice', 'Carol']
Spot the Bug click the line that contains the error

This code should use zip() to pair names with scores and return only the names where the score is 70 or above. Something is wrong. Find the line causing the problem.

names = ["Alice", "Bob", "Carol", "Dan"]
scores = [88, 65, 91, 72]
passing = [name for name, score in zip(names, scores) if scores >= 70]
print(passing)
The bug is on line 3. The condition reads if scores >= 70 but it should be if score >= 70. scores is the whole list — comparing a list to an integer raises a TypeError. The loop variable unpacked from zip() is score (singular), not scores. The fix: change scores >= 70 to score >= 70.
Check Your Understanding question 1 of 4

Variable Scope

In Python 3, the loop variable inside a list comprehension is isolated from the surrounding code and does not leak into it. Writing [x for x in range(5)] does not leave a variable x accessible afterward — unlike a regular for loop, which does leave the loop variable in scope after the loop finishes. This isolation was introduced in Python 3 (Python 2 comprehensions did leak). In Python 3.12 and later, the implementation changed from a hidden nested function to inlined bytecode (PEP 709), but the isolation guarantee is unchanged: the LOAD_FAST_AND_CLEAR instruction saves any outer variable with the same name before the comprehension runs, and STORE_FAST restores it afterward.

List Comprehension vs. Generator Expression

Changing the outer square brackets of a list comprehension to parentheses produces a generator expression. The syntax looks almost identical, but the behavior is fundamentally different.

PEP 202, the proposal that introduced list comprehensions in Python 2.0, aimed to give developers a more compact syntax for building lists without the boilerplate of the loop-and-append pattern. That intention — conciseness in service of clarity — remains the guiding principle: a comprehension should not need a comment to explain itself.

python
# List comprehension — builds the entire list immediately in memory
squares_list = [x ** 2 for x in range(1000000)]
# All one million values exist in memory right now

# Generator expression — produces values one at a time on demand
squares_gen = (x ** 2 for x in range(1000000))
# No values are computed yet; the generator waits to be consumed
Aspect List comprehension [ ] Generator expression ( )
Result type list generator object
When values are computed All at once, immediately One at a time, on demand (lazy)
Memory use Holds all items in memory Holds only one item at a time
Reusable? Yes — iterate as many times as you like No — exhausted after one pass
Supports indexing? Yes — result[0] works No — must consume in order
Best for When you need the full list or will iterate more than once When you only need to pass values one-by-one (e.g. to sum(), max())

If you are immediately passing the result to a function that consumes it once — like sum(), max(), or any() — a generator expression is more memory-efficient and equally readable. If you need to index into the result, iterate it multiple times, or pass it to code that expects a list, use a list comprehension.

python
nums = [1, 2, 3, 4, 5]

# Generator expression passed directly to sum() — no list ever built
total = sum(x ** 2 for x in nums)
print(total)
# 55

# List comprehension needed here — we index into the result
squares = [x ** 2 for x in nums]
print(squares[2])
# 9

Common Mistakes

A few patterns consistently trip up newcomers to list comprehensions.

Watch Out

Using curly braces {} instead of square brackets [] creates a set or dictionary, not a list. Always use [ and ] for list comprehensions.

Putting the condition in the wrong place

python
# WRONG — condition before the for clause is a ternary, not a filter
result = [x if x > 0 for x in nums]  # SyntaxError

# CORRECT — filter condition goes after the for clause
result = [x for x in nums if x > 0]

Confusing filtering with ternary expressions

python
nums = [-2, 3, -1, 4]

# This filters — produces only positive numbers
positives = [x for x in nums if x > 0]
# [3, 4]

# This uses a ternary — every item is kept, negatives become 0
clipped = [x if x > 0 else 0 for x in nums]
# [0, 3, 0, 4]
Note

A ternary expression (value_if_true if condition else value_if_false) goes in the expression position, before the for clause. A filter (if condition alone) goes after the for clause. They are different constructs that produce different results.

Nesting too deeply

python
# Hard to read — avoid this
result = [x * y for x in range(3) for y in range(3) if x != y]

# Easier to read as an explicit loop
result = []
for x in range(3):
    for y in range(3):
        if x != y:
            result.append(x * y)

Using or in a condition — it does not work like two separate filters

Chained if clauses act as and, not or. If you want items that match either of two conditions, you must use the or operator inside a single if clause — not two separate if clauses.

python
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# CORRECT — use 'or' in a single if clause to keep items matching either condition
result = [x for x in nums if x < 3 or x > 8]
print(result)
# [1, 2, 9, 10]

# WRONG MENTAL MODEL — two chained if clauses act as 'and', not 'or'
# This keeps only items where BOTH conditions are true simultaneously,
# which is impossible for a single integer — produces an empty list
result = [x for x in nums if x < 3 if x > 8]
print(result)
# []

Using a comprehension for side effects instead of building a list

Functions like print(), list.sort(), and file.write() return None. If you call them in the expression position, the comprehension will collect a list of None values — which is almost certainly not what you intended, and it wastes memory building a list you never use. Use a plain for loop when you need side effects.

python
items = ["apple", "banana", "cherry"]

# WRONG — builds a useless list of None; print() returns None
result = [print(item) for item in items]
print(result)
# apple
# banana
# cherry
# [None, None, None]  ← this is the actual list comprehension produced

# CORRECT — use a plain for loop when the goal is side effects, not a new list
for item in items:
    print(item)

Under the Hood: What CPython Actually Does

Most Python resources explain what list comprehensions do without explaining how CPython executes them differently from a for loop. The difference comes down to bytecode — and how that bytecode has evolved.

In all Python versions, the key instruction is LIST_APPEND: a dedicated bytecode opcode that adds a value to the list being built. A regular for loop calling result.append(item) must perform a full attribute lookup on the result list object every iteration — Python resolves .append as an attribute, retrieves the bound method, and then calls it. LIST_APPEND skips that attribute resolution entirely, which is the primary reason comprehensions benchmark faster than equivalent loops.

Python 3.12 changed the execution model significantly. Before 3.12, a list comprehension was compiled as a separate code object — essentially a hidden nested function that was created, called once, and immediately discarded. PEP 709, implemented in Python 3.12, changed this: comprehensions are now inlined directly into the surrounding function's bytecode. There is no longer a separate code object, no temporary function object allocation, and no new Python frame pushed onto the stack. The measured result was up to 2x faster in microbenchmarks and approximately 11% faster across real-world benchmark suites that use comprehensions heavily. Variable isolation — the loop variable not leaking into the outer scope — is preserved in 3.12+ through a pair of new instructions (LOAD_FAST_AND_CLEAR and STORE_FAST) that save and restore any outer variable with the same name on the stack.

You can inspect this directly using the dis module, which is part of Python's standard library. In Python 3.12 and later, running dis.dis() on a function containing a comprehension will show the inlined bytecode — including LOAD_FAST_AND_CLEAR, BUILD_LIST, LIST_APPEND, and STORE_FAST — all within the same code object as the surrounding function rather than in a nested one:

python
import dis

# Disassemble a list comprehension
dis.dis(lambda: [x * 2 for x in range(5)])

# Disassemble an equivalent for loop
def with_loop():
    result = []
    for x in range(5):
        result.append(x * 2)
    return result

dis.dis(with_loop)

Running this in Python 3.12+ shows the comprehension inlined into the outer function, using LIST_APPEND directly, while the loop version shows explicit LOAD_ATTR and CALL instructions for result.append. This is not a micro-optimization you should chase — it is simply the explanation for why list comprehensions benchmark faster, and why that speed advantage widened further in Python 3.12.

What changed in Python 3.12 vs older versions

Before Python 3.12, a list comprehension was compiled as a hidden nested function — a separate code object allocated with MAKE_FUNCTION, called immediately, and then discarded. Every comprehension therefore involved a full Python function-call roundtrip. PEP 709 (Python 3.12) removed that overhead by inlining the comprehension directly into the surrounding function's bytecode. The practical effect: up to 2x faster in microbenchmarks of the comprehension itself, and about 11% faster across real-world benchmark suites that use comprehensions heavily, according to the PEP's reference implementation results. Variable isolation — the loop variable not leaking into surrounding scope — is preserved through the LOAD_FAST_AND_CLEAR and STORE_FAST instructions. If you are on Python 3.11 or earlier, the description in older tutorials (a hidden nested function) was accurate for your version; if you are on 3.12 or later, the comprehension runs inline.

List comprehensions were introduced in PEP 202, authored by Barry Warsaw and shipped in Python 2.0. The PEP described list comprehensions as providing "a more concise way to create lists." The scope behavior — where the loop variable is isolated and does not leak into surrounding code — was introduced in Python 3 as a deliberate improvement over Python 2, where comprehension variables did leak. In Python 3.12, the implementation mechanism changed (from a nested function to inlined bytecode via PEP 709), but the isolation guarantee was preserved. The official reference is the Python 3 documentation on data structures.

Beyond Lists: Dict and Set Comprehensions

Once you are comfortable with list comprehensions, the same syntax extends to two other Python built-in types with only a bracket change. You will encounter both in real Python code immediately after leaving beginner territory, so it is worth knowing they exist.

A dictionary comprehension uses curly braces and a key: value pair as the expression. A set comprehension uses curly braces with a single expression — like a list comprehension but with {} instead of [], which produces a deduplicated, unordered collection.

python
# List comprehension — square brackets, returns a list
squares_list = [x ** 2 for x in range(5)]
print(squares_list)
# [0, 1, 4, 9, 16]

# Dictionary comprehension — curly braces + key:value, returns a dict
squares_dict = {x: x ** 2 for x in range(5)}
print(squares_dict)
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Set comprehension — curly braces + single expression, returns a set (no duplicates)
letters = {ch.lower() for ch in "Mississippi"}
print(letters)
# {'m', 'i', 's', 'p'}  — order may vary; duplicates removed
How to tell them apart at a glance

[...] — list comprehension, always ordered, allows duplicates. {key: value ...} — dict comprehension, maps keys to values. {expression ...} — set comprehension, deduplicated, unordered. An empty {} with no for clause is a dict literal, not a set. The rules for the for clause, the optional if filter, and the expression position are identical across all three forms — everything you learned about list comprehensions transfers directly.

Key Takeaways

  1. A list comprehension builds a new list from an existing iterable in a single expression using the form [expression for item in iterable].
  2. The three parts are expression, loop clause, and optional filter. The expression defines what goes in the list; the loop clause defines what you loop over; the condition filters which items are included.
  3. List comprehensions and for loops produce the same result. Use comprehensions for simple, readable transformations and filters; use loops when the logic is complex or requires side effects.
  4. The condition always goes after the for clause. Placing if condition before the for keyword is either a syntax error or a ternary expression — two different things.
  5. Comprehensions are generally faster than equivalent for loops because CPython uses optimized internal instructions for list building rather than the full attribute-lookup overhead of list.append(). Python 3.12 made them faster still by eliminating hidden function-call overhead (PEP 709). In everyday code, readability matters more than this speed difference.
  6. The loop variable does not leak. In Python 3, the iteration variable inside a comprehension is isolated from the surrounding scope — by design since Python 3.0, and preserved through new bytecode mechanics in Python 3.12+.
  7. Never use a comprehension for side effects. Calling print() or any function that returns None in the expression position builds a useless list of None values. Use a plain for loop when the goal is side effects, not a new list.
  8. The same syntax extends to dicts and sets. {k: v for ...} is a dict comprehension; {expr for ...} is a set comprehension. Every rule you learned here applies to both.
  9. Readability should guide the choice. If a comprehension requires a second read to understand, a regular for loop is the better option.

List comprehensions are most useful when the transformation or filter is simple enough to read at a glance. The goal is clear, expressive code — not the shortest possible line count.

Certificate of Completion
Final Exam
Pass mark: 80% · Score 80% or higher to receive your certificate

Enter your name as you want it to appear on your certificate, then start the exam. Your name is used only to generate your certificate and is never transmitted or stored anywhere.

Question 1 of 10

Frequently Asked Questions

A list comprehension is a concise way to create a new list by applying an expression to each item in an iterable, all written in a single line. The syntax is [expression for item in iterable]. It produces the same result as a for loop that appends items to a list, but in fewer lines of code.
A for loop builds a list by first creating an empty list, then appending items one by one inside the loop body. A list comprehension does the same thing in a single expression without needing an empty list or an append call. For simple transformations and filters, list comprehensions are more compact and generally faster because Python evaluates them with optimized internal bytecode.
Yes. You can add a filtering condition at the end of the comprehension: [expression for item in iterable if condition]. Only items for which the condition is True will be included in the resulting list. For example, [x for x in range(10) if x % 2 == 0] produces [0, 2, 4, 6, 8].
The full syntax is: [expression for item in iterable if condition]. The expression is what goes into the new list. The for clause loops over a sequence. The if condition is optional and filters which items are included. Square brackets always wrap the entire comprehension.
List comprehensions become hard to read when they contain multiple nested loops or complex logic spanning more than one or two conditions. If you need to use a multi-line expression, add side effects inside the loop, or the logic requires explanation to understand, a regular for loop is the clearer choice. Python style guidelines favor readability over brevity.
Yes, you can nest list comprehensions to iterate over multiple sequences, which is equivalent to nested for loops. However, deeply nested comprehensions are difficult to read and are generally better replaced with explicit for loops when more than one level of nesting is involved.
Yes. Strings are iterable in Python, so you can loop over individual characters. For example, [char.upper() for char in 'hello'] produces ['H', 'E', 'L', 'L', 'O']. You can also loop over a list of strings and apply string methods to each one.
Yes, and measurably so. CPython compiles list comprehensions with a dedicated LIST_APPEND bytecode instruction that bypasses the attribute lookup and method-call overhead of list.append(). Benchmarks on Python 3.12 consistently show comprehensions running 20–40% faster than equivalent for loops for list-building. Python 3.12 also introduced PEP 709, which inlines comprehensions directly into the surrounding function's bytecode — eliminating the hidden nested function that older versions created — for up to 2x faster execution in microbenchmarks. The gap is small on short lists and most noticeable when building lists of thousands of items. Readability should still take priority over chasing this speed difference in everyday code.
Yes. You can chain multiple if clauses: [x for x in nums if x > 0 if x % 2 == 0]. Python treats chained conditions as a logical and — every condition must be true for an item to be included. You can also write the same logic as a single condition using and: [x for x in nums if x > 0 and x % 2 == 0]. Most style guides prefer the single-condition form with and for readability.
A list comprehension uses square brackets and builds the entire list in memory immediately. A generator expression uses parentheses and produces values one at a time on demand, without storing them all at once. Use a generator expression when you only need to pass the result to a function like sum() or max() that consumes it once. Use a list comprehension when you need to index into the result, iterate it more than once, or pass it to code that expects a list.
No. A list comprehension always produces a brand new list. The original iterable is never modified. For example, doubled = [x * 2 for x in nums] creates a separate list called doubled while leaving nums unchanged. If you need to update a list in place, you would reassign the variable: nums = [x * 2 for x in nums].
Yes. Both are iterables, so they work anywhere you can write a for clause. With enumerate(), you can unpack the index and value directly: [f"{i}: {v}" for i, v in enumerate(my_list)]. With zip(), you can loop over two lists simultaneously: [a + b for a, b in zip(list_a, list_b)]. These patterns are common in real Python code and are a natural extension of what you have already learned.
Yes. The expression position accepts any valid Python expression, including calls to functions you define yourself. For example, [clean(s) for s in raw_data] calls clean() on every item. This is a common real-world pattern: write a function that handles one item cleanly, then use a comprehension to apply it across a whole list. Built-in functions like int(), str(), and len() work the same way.
It returns an empty list — [] — and raises no error. This is consistent behavior you can rely on. [x * 2 for x in []] produces [], and [x for x in range(5) if x > 100] also produces [] because the filter removes every item. A comprehension will never crash just because its input is empty or all items are filtered out.
Yes, but only inside a single if clause. [x for x in nums if x < 3 or x > 8] keeps items satisfying either condition. The important thing to know is that chained if clauses act as and, not or: [x for x in nums if x < 3 if x > 8] requires both conditions to be true simultaneously, which produces an empty list for most inputs. If you need or logic, write it inside a single if using the or operator.
Yes. The same for clause and optional if filter work in all three forms. The only differences are the bracket type and the expression format. A dict comprehension uses {key: value for item in iterable} and returns a dictionary. A set comprehension uses {expression for item in iterable} and returns a set (unordered, no duplicates). Everything you learn about list comprehension syntax applies directly to both forms.

How to Write a List Comprehension in Python

Use this reference whenever you are building a new comprehension from scratch. Each step maps directly to one part of the [expression for item in iterable if condition] structure.

  1. Open square brackets to start the comprehension

    Every list comprehension is wrapped in square brackets. Type an opening bracket [ to signal to Python that you are building a list, not a tuple or a set. The entire comprehension — expression, loop clause, and any condition — lives inside these brackets.

  2. Write the expression — what goes into each slot of the new list

    The expression is the first thing inside the brackets. It defines what value gets added to the new list for each iteration. This can be the loop variable itself (x), a calculation (x ** 2), a method call (x.upper()), or any valid Python expression. Write it before the for keyword.

  3. Add the for clause to define the loop variable and source sequence

    After the expression, write for item in iterable. Replace item with any variable name you choose and iterable with the list, range, string, or other sequence you want to loop over. This clause is required in every comprehension — there is no such thing as a comprehension without a for.

  4. Optionally add an if condition to filter items

    If you only want certain items to reach the expression, add if condition after the for clause. Only items for which the condition evaluates to True are processed and added to the new list. This is a filter — there is no else branch here. If you need an else, that is a ternary expression and belongs in the expression position before for, not at the end.

  5. Close the brackets and assign the result

    Type the closing square bracket ] to complete the comprehension. Assign the whole expression to a variable: result = [expression for item in iterable]. The variable now holds a brand new list. The original iterable is never modified — comprehensions always produce a fresh list object.

  6. Verify readability — convert back to a loop if needed

    Read the finished comprehension aloud. If it takes more than one pass to understand what it does, rewrite it as a standard for loop. A comprehension that requires a comment to explain itself is better expressed as a loop. Readability is the deciding criterion — not brevity, and not the desire to fit logic into one line.