Python Dictionary Comprehensions: When They Help and When They Hurt
A dictionary comprehension builds a dictionary in one line. Good for transforming, filtering and building lookups. Bad for nested logic, side effects and anything that needs error handling. Shorter code is not always better code.
A dictionary comprehension builds a dictionary in one line. Used well, it's shorter and clearer than a loop. Used badly, it's a puzzle you have to solve every time you read it. Shorter code is not always better code. My rule is simple: if I can't read it the next day, I write the loop.
Skip ahead: If you know the {key: value for item in items} syntax and only want the judgment part, go to "When they hurt".
The syntax
prices = {"coffee": 95, "ensaymada": 35.5, "cake": 1250}
with_vat = {name: round(price * 1.12, 2) for name, price in prices.items()}
print(with_vat)
{'coffee': 106.4, 'ensaymada': 39.76, 'cake': 1400.0}
Read it left to right: "make name: price with VAT, for each name, price in prices.items()". It is exactly this loop:
prices = {"coffee": 95, "ensaymada": 35.5, "cake": 1250}
with_vat = {}
for name, price in prices.items():
with_vat[name] = round(price * 1.12, 2)
print(with_vat)
{'coffee': 106.4, 'ensaymada': 39.76, 'cake': 1400.0}
Same result. The comprehension says it in one line, and it doesn't make you read three lines to find out it's "the same dictionary, transformed".
When they help
1. Transforming every value or key. Like the VAT example. One input, one output, one simple rule.
2. Filtering. Add an if at the end:
stock = {"coffee": 40, "ensaymada": 0, "cake": 3, "water": 0}
available = {name: qty for name, qty in stock.items() if qty > 0}
print(available)
{'coffee': 40, 'cake': 3}
3. Building a lookup from a list. This is the one I use most. You have a list of records and you keep searching it by id. Build a dictionary once and look things up directly:
customers = [
{"id": 7, "name": "Ana Reyes"},
{"id": 12, "name": "Ben Cruz"},
{"id": 31, "name": "Carla Santos"},
]
by_id = {customer["id"]: customer for customer in customers}
print(by_id[12]["name"])
Ben Cruz
Searching a list checks every item until it finds a match. A dictionary goes straight to it. With three customers, nobody cares. With fifty thousand, inside a loop, you'll care a lot.
⚠ Warning: If two records have the same key, the last one silently wins. No error. If ids can repeat in your data, a lookup dictionary hides the duplicates. Check len(by_id) == len(customers) once, and if it's not equal, find out why before you trust the lookup.
When they hurt
1. Nested logic. This works:
orders = [
{"customer": "Ana", "status": "paid", "total": 190},
{"customer": "Ben", "status": "unpaid", "total": 426},
{"customer": "Ana", "status": "paid", "total": 1250},
{"customer": "Carla", "status": "cancelled", "total": 95},
]
summary = {c: sum(o["total"] for o in orders if o["customer"] == c and o["status"] == "paid") for c in {o["customer"] for o in orders} if any(o["customer"] == c and o["status"] == "paid" for o in orders)}
print(dict(sorted(summary.items())))
{'Ana': 1440}
It's correct. And I can't read it without stopping. It also goes through the list again for every customer. Here's the loop:
orders = [
{"customer": "Ana", "status": "paid", "total": 190},
{"customer": "Ben", "status": "unpaid", "total": 426},
{"customer": "Ana", "status": "paid", "total": 1250},
{"customer": "Carla", "status": "cancelled", "total": 95},
]
paid_totals = {}
for order in orders:
if order["status"] != "paid":
continue
name = order["customer"]
paid_totals[name] = paid_totals.get(name, 0) + order["total"]
print(paid_totals)
python good.py
{'Ana': 1440}
More lines, but each one says one thing. You can put a print in the middle when something's off. You can't do that inside a comprehension. When a dictionary is built up over time, adding to the same key again and again, write a loop. A comprehension is for one key, one value, per item.
2. Side effects. A comprehension is for building a dictionary, not for doing things. If you write {name: send_email(name) for name in customers} to send emails, the dictionary is just a pile of return values nobody wanted. Use a for loop when the point is the action.
3. Error handling. You can't put try/except inside a comprehension. If some values might fail to convert, like prices typed as text, use a loop, or a small helper function that handles the error and returns a clear value.
A simple test before you write one
Ask three questions:
- Is it one key and one value per item?
- Is there at most one
if? - Does it fit on one line, or two at most, without squinting?
Three yeses: use the comprehension. Any no: write the loop. It's not about being clever. The next person reading the code is usually you, three months later, tired.
💡 Tip: If a comprehension gets too long, the fix is often not a loop but a function. {name: price_with_vat(price) for name, price in prices.items()} stays readable because the hard part has a name.
Common mistakes
- Using
[]instead of{}.[name for name in prices]gives a list. - Forgetting
.items().for name, price in pricesloops over keys only, and Python tries to unpack each key string into two names. - Duplicate keys. The last one wins, quietly.
- Nested comprehensions to look smart. It doesn't look smart to the person who has to fix it.
What happens if you get this wrong
Nothing, at first. The code runs. The cost shows up later, when someone needs to change it and can't figure out what it does without rewriting it. Readable code is cheaper to fix. Clever code is cheaper to write once and expensive every time after.
About
The comprehension syntax, the three cases where it helps, the three where a loop is better, and a three-question test to decide. After this, you can write comprehensions that people can still read the next day.
- Responsible
- John G
- Last Update
- 09/15/2026
- Reading Time
- 5 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.