When a Comprehension Should Be a Plain Loop
Where comprehensions stop helping: nested logic, side effects, and errors you need to handle. After this, you can decide case by case whether one line or a loop is the better code.
Course content
Sign in to save your progress.
-
Set Up Projects That Don't Break Each Other
-
Python Virtual Environments and pip: One Project, One Environment
-
Pinning Python Dependencies With requirements.txt
-
-
-
Organize Code Other People Can Read
-
Python Modules and Packages: Splitting One Big File
-
Python Imports: Why ModuleNotFoundError Keeps Happening
-
Python Classes: When a Dictionary Is No Longer Enough
-
-
-
Work With Real Files
-
Python File Handling: Open, Read, Write, and Always Use with
-
Python pathlib: Stop Building File Paths by Hand
-
Reading, Cleaning and Writing CSV Data in Python
-
-
-
Shorter Code That Still Says What It Does
-
Python Comprehensions: Lists, Sets and Dictionaries
-
When a Comprehension Should Be a Plain Loop
-
-
-
Prove It Works Before Someone Else Finds Out
-
Python Testing Basics With pytest
-
Debugging Python With print, logging and pdb
-
-
- Final Check: Python Intermediate
Shorter code is not always better code. A comprehension is good when it reads as one sentence. Once it needs nested loops, several conditions, error handling or a side effect, a plain loop is usually clearer, and clearer wins. If you can't read it the next day, write the loop.
Skip ahead: If you already have a rule for when to stop using comprehensions and know why exceptions inside them are a problem, take the section quiz.
A simple test
Read the comprehension out loud. If it sounds like one normal sentence, keep it. "The names of active customers" is [c.name for c in customers if c.active]. Good. If reading it out loud needs "and then", "unless", or a breath in the middle, it wants to be a loop.
This is a guideline, not a law. Some teams accept longer comprehensions than others. The point is to decide on purpose instead of compressing everything because you can.
Case 1: nested loops
orders = [
{"id": 1, "items": [{"sku": "KB-01", "qty": 1}, {"sku": "MS-02", "qty": 2}]},
{"id": 2, "items": [{"sku": "MS-02", "qty": 1}]},
]
skus = [item["sku"] for order in orders for item in order["items"] if item["qty"] > 1]
print(skus)
skus = []
for order in orders:
for item in order["items"]:
if item["qty"] > 1:
skus.append(item["sku"])
print(skus)
['MS-02']
['MS-02']
Both are correct. The order of the for parts in the comprehension matches the loop version from top to bottom, which most people can't recall without checking. One nested level is borderline. Two or more, write the loop.
Case 2: errors you need to handle
You can't put try/except inside a comprehension. So one bad value stops everything, and you lose the information about which value it was:
raw = ["1200", "450", "N/A", "89"]
try:
amounts = [float(x) for x in raw]
except ValueError as error:
print("Failed:", error)
amounts, bad = [], []
for position, x in enumerate(raw):
try:
amounts.append(float(x))
except ValueError:
bad.append((position, x))
print(amounts, bad)
Failed: could not convert string to float: 'N/A'
[1200.0, 450.0, 89.0] [(2, 'N/A')]
The loop keeps the good values and records the bad one with its position. For real data, that is almost always what you want, as the CSV lesson showed.
You may see a workaround: a helper function that returns None on error, used inside the comprehension. It works. It also hides the error unless you filter and report the None values separately. At that point the loop is shorter.
Case 3: side effects
emails = ["ana@example.com", "ben@example.com"]
result = [print("Sending to", e) for e in emails]
print(result)
Sending to ana@example.com
Sending to ben@example.com
[None, None]
A comprehension is for building a value. Printing, sending, saving or deleting are actions, and the list of None values is useless. Write for e in emails: send(e). A reader seeing a comprehension expects nothing to happen except a new list. Breaking that expectation is how real bugs get missed in code review.
Case 4: too many conditions
customers = [
{"name": "Ana", "active": True, "country": "PH", "spent": 15000, "vip": False},
{"name": "Ben", "active": True, "country": "SG", "spent": 2000, "vip": True},
{"name": "Cy", "active": False, "country": "PH", "spent": 30000, "vip": False},
]
targets = [c["name"].upper() if c["vip"] else c["name"] for c in customers if c["active"] and (c["country"] == "PH" and c["spent"] > 10000 or c["vip"])]
print(targets)
def qualifies(c):
if not c["active"]:
return False
if c["vip"]:
return True
return c["country"] == "PH" and c["spent"] > 10000
targets = [c["name"].upper() if c["vip"] else c["name"] for c in customers if qualifies(c)]
print(targets)
['Ana', 'BEN']
['Ana', 'BEN']
Notice the middle option. You don't always have to choose between one giant line and a full loop. Moving the condition into a named function keeps the comprehension short, and qualifies can be tested on its own.
What about speed?
Comprehensions are often a little faster than the same append loop, because Python runs them with less overhead per item. Let's measure instead of claiming:
import timeit
loop = """
result = []
for n in range(1000):
result.append(n * 2)
"""
comp = "result = [n * 2 for n in range(1000)]"
t_loop = timeit.timeit(loop, number=5000)
t_comp = timeit.timeit(comp, number=5000)
print("comprehension faster:", t_comp < t_loop)
comprehension faster: True
On the computer this course was tested on, the comprehension was faster, but the exact numbers depend on your machine and Python version, so measure your own. More important: for most scripts, the difference is microseconds. If a loop with real work in it (a database call, a file read) is slow, the comprehension won't save you. Choose for readability, and measure before optimizing. That is a whole lesson in Python Advanced.
⚠ Warning: Don't "clean up" a working loop into a comprehension in code that handles money, deletions or anything else risky, unless there are tests around it. Converting nested loops with conditions is exactly where the order of for and if gets swapped by mistake, and the result looks right on small test data.
Common mistakes
- Compressing code to look clever. The next person to read it, maybe you in three months, pays for it.
- Expanding every comprehension back into loops. The opposite mistake. A short, clear comprehension is better than four lines of loop.
- Using the walrus operator (
:=) to squeeze more in. It has good uses, but if it is there to avoid writing a loop, write the loop.
💡 Tip: If a comprehension needs a line break to fit on screen, that is a hint, not a formatting problem. Try a named function for the condition first, then a loop.
What happens if you skip this step
The code works, and it is unreadable. Bugs hide inside long one-liners because reviewers skim them. A bad value crashes the whole conversion with no hint of which row it was. You save three lines and lose an hour the first time something goes wrong.
Find the longest comprehension in your current project. Could someone new to the code explain what it does in under ten seconds?
About
Where comprehensions stop helping: nested logic, side effects, and errors you need to handle. After this, you can decide case by case whether one line or a loop is the better code.
- Duration
- 5 min
- Last update
- 09/11/2026
Comments (0)
Sign in to commentNo comments yet.