Skip to main content

List Comprehensions

If you're new to programming

A list comprehension is a shortcut for building a list. Instead of writing a loop that appends items one at a time, you describe the entire list in a single line. It reads like a sentence: "give me [this] for each [item] in [collection] if [condition]."

If you've coded before

List comprehension syntax: [expression for item in iterable if condition]. This lesson covers basic comprehensions, filtering conditions, and readability guidelines. Nested comprehensions are mentioned but discouraged.

James opens search.py from his SmartNotes package. The filter_by_tag function is four lines:

def filter_by_tag(notes: list[Note], tag: str) -> list[Note]:
result: list[Note] = []
for note in notes:
if tag in note.tags:
result.append(note)
return result

"Four lines to say 'give me all notes that have this tag,'" he says.

Emma writes one line on the whiteboard:

[note for note in notes if tag in note.tags]

"Same thing," she says. "One line."


The Pattern: Loop → Comprehension

Every list-building loop follows the same pattern:

# The loop pattern
result = []
for item in collection:
if condition:
result.append(expression)

The comprehension compresses this into one expression:

result = [expression for item in collection if condition]

Read it left to right:

PartMeaningExample
expressionWhat to includenote (the whole object)
for item in collectionWhat to loop overfor note in notes
if conditionWhat to filter (optional)if tag in note.tags

Your First Comprehension

Start simple. Double every number in a list:

numbers = [1, 2, 3, 4, 5]

# Loop version
doubled = []
for n in numbers:
doubled.append(n * 2)

# Comprehension version
doubled = [n * 2 for n in numbers]

print(doubled)

Output:

[2, 4, 6, 8, 10]

Same result. The comprehension reads: "give me n * 2 for each n in numbers."


Adding a Filter

Keep only even numbers:

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

# Loop version
evens = []
for n in numbers:
if n % 2 == 0:
evens.append(n)

# Comprehension version
evens = [n for n in numbers if n % 2 == 0]

print(evens)

Output:

[2, 4, 6, 8, 10]

The if n % 2 == 0 at the end filters out odd numbers. Only items that pass the condition are included.


Comprehensions with SmartNotes

Rewrite the SmartNotes filter functions:

from dataclasses import dataclass, field

@dataclass
class Note:
title: str
body: str
word_count: int
author: str = "Anonymous"
is_draft: bool = True
tags: list[str] = field(default_factory=list)


# Before: loop
def filter_by_tag_loop(notes: list[Note], tag: str) -> list[Note]:
result: list[Note] = []
for note in notes:
if tag in note.tags:
result.append(note)
return result

# After: comprehension
def filter_by_tag(notes: list[Note], tag: str) -> list[Note]:
return [note for note in notes if tag in note.tags]

Both functions do exactly the same thing. The comprehension is one line instead of four.

More SmartNotes examples:

# Get titles of all non-draft notes
titles = [note.title for note in notes if not note.is_draft]

# Get word counts as a list
counts = [note.word_count for note in notes]

# Get notes by a specific author
james_notes = [note for note in notes if note.author == "James"]

# Get titles of long notes (100+ words)
long_titles = [note.title for note in notes if note.word_count >= 100]

Test them:

notes = [
Note("Python Tips", "Learn basics", 50, "James", True, ["python"]),
Note("Debug Guide", "Fix errors", 120, "James", False, ["python", "debug"]),
Note("Cooking", "Boil water", 30, "Emma", False, ["cooking"]),
]

print([note.title for note in notes if not note.is_draft])
print([note.word_count for note in notes])
print([note.title for note in notes if note.author == "James"])

Output:

['Debug Guide', 'Cooking']
[50, 120, 30]
['Python Tips', 'Debug Guide']

Transform + Filter Together

Comprehensions can transform and filter in the same expression:

# Get word counts of Python notes only
python_counts = [note.word_count for note in notes if "python" in note.tags]
print(python_counts) # [50, 120]

# Get uppercase titles of drafts
draft_titles = [note.title.upper() for note in notes if note.is_draft]
print(draft_titles) # ['PYTHON TIPS']

# Build summary strings
summaries = [
f"{note.title} ({note.word_count} words)"
for note in notes
if note.word_count > 40
]
print(summaries) # ['Python Tips (50 words)', 'Debug Guide (120 words)']

The expression part (before for) does the transformation. The if part does the filtering. Together they handle most data processing tasks in one line.


When NOT to Use Comprehensions

Comprehensions are powerful but not always the best choice:

Use a comprehension when:

  • The logic is simple (one filter, one transformation)
  • The result fits on one or two lines
  • A reader can understand it in five seconds

Use a loop when:

  • The logic has multiple steps
  • You need try/except inside the loop
  • The comprehension would span three or more lines
  • You need to track state (running totals, counters)
# 🟢 Good comprehension: clear, simple
titles = [note.title for note in notes if not note.is_draft]

# 🔴 Bad comprehension: too complex, hard to read
result = [
note.title.lower().replace(" ", "-")
for note in notes
if note.author == "James" and not note.is_draft and note.word_count > 50
and "python" in note.tags
]

# 🟢 Better as a loop:
result = []
for note in notes:
is_james = note.author == "James"
is_published = not note.is_draft
is_long = note.word_count > 50
has_python_tag = "python" in note.tags
if is_james and is_published and is_long and has_python_tag:
result.append(note.title.lower().replace(" ", "-"))

The loop version names each condition, making the logic readable. The comprehension version packs everything onto one line and forces the reader to untangle it.


PRIMM-AI+ Practice: Predict the Comprehension

Predict [AI-FREE]

Press Shift+Tab to enter Plan Mode.

What does each comprehension produce?

words = ["hello", "WORLD", "Python", "ai"]

# A
result_a = [w.upper() for w in words]

# B
result_b = [w for w in words if len(w) > 3]

# C
result_c = [len(w) for w in words]

# D
result_d = [w.lower() for w in words if w[0].isupper()]

Write your predictions for A, B, C, D. Rate confidence 1-5.

Check your predictions
# A: ['HELLO', 'WORLD', 'PYTHON', 'AI']
# B: ['hello', 'WORLD', 'Python']
# C: [5, 5, 6, 2]
# D: ['world', 'python']

A transforms all words to uppercase. B filters words longer than 3 characters. C replaces each word with its length. D filters words starting with uppercase, then lowercases them.

Run

Press Shift+Tab to exit Plan Mode.

Create comprehension_practice.py and verify your predictions.

Investigate

If you want to go deeper, run /investigate @comprehension_practice.py in Claude Code and ask: "Can I use an else clause in a list comprehension? What is the difference between a filter (if after for) and a conditional expression (if/else before for)?"

The conditional expression (a if cond else b) goes before for. The filter (if cond) goes after for. They serve different purposes.

Modify

Rewrite exclude_drafts and sort_by_word_count from Chapter 62 as comprehensions (or comprehension + sorted()). Verify the output matches the loop versions.

Make [Mastery Gate]

Write a function tag_summary(notes: list[Note]) -> list[str] that returns a sorted list of unique tags across all notes. Use a comprehension to extract all tags, then convert to a set, then sort. In Claude Code, type /tdg to guide you through the cycle:

  1. Write the stub
  2. Write 3+ tests (no notes, notes with overlapping tags, sorting)
  3. Prompt AI to implement
  4. Verify with ruff, pyright, pytest

Try With AI

Opening Claude Code

If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.

Prompt 1: Loop to Comprehension

Here are three functions from my SmartNotes project that
use loops. Rewrite each as a list comprehension. For each
one, tell me if the comprehension is more readable or if
I should keep the loop.

[paste your loop-based functions]

What you're learning: Not every loop should become a comprehension. The AI makes the judgment call for each case and explains why. You develop the same judgment.

Prompt 2: Comprehension Performance

Is a list comprehension faster than a for loop in Python?
By how much? Show me a benchmark comparing the two for
a list of 10,000 items.

What you're learning: Comprehensions are slightly faster (10-30%) than equivalent loops because Python optimizes the bytecode. But readability matters more than micro-optimization for most code.

Prompt 3: Nested Comprehensions

Show me a nested list comprehension that flattens a list
of lists: [[1, 2], [3, 4], [5, 6]] → [1, 2, 3, 4, 5, 6].
Then show the equivalent nested for loop. Which is more
readable? When would you use the nested comprehension?

What you're learning: Nested comprehensions exist but are rarely the best choice. The AI helps you see both options and build judgment about when nesting helps vs. hurts readability.


James rewrites three SmartNotes functions using comprehensions. Each shrinks from four lines to one. He runs the tests. All green.

"It reads like English," he says. "[note.title for note in notes if not note.is_draft]. Give me the title of each note that is not a draft."

"That is the power of comprehensions," Emma says. "They express intent. The loop says how to build the list. The comprehension says what the list contains."

"But I tried to write a comprehension for the pipeline function and it became three lines long with two conditions and a method chain," James says. "That was harder to read than the loop."

"Good judgment," Emma says. "Comprehensions are for simple transformations. Complex logic stays in loops. Knowing which to use is the skill."

She draws two new brackets on the whiteboard: curly braces.

"Lists use square brackets. Dictionaries and sets use curly braces. Both support comprehensions. Lesson 2 teaches you how."