A dictionary comprehension is Python's one-line form for building a dict from any iterable, with optional filtering and key/value transforms. The shape mirrors a list comprehension, but instead of producing items, each pass produces a key: value pair. This lesson covers the syntax, the common patterns you'll actually use, and the moment to drop the comprehension and write a plain loop instead.
The pattern shows up in almost every program that handles structured data: start with an empty dict, walk an iterable, and put a key with its value into the dict on each pass. Here's the long version. A list of product names should turn into a dict mapping each name to the length of its name (a quick proxy for how wide the column needs to be in a printed catalog).
Four lines of scaffolding around a single mapping. The dict comprehension says the same thing in one expression:
Read it left to right: "give me name: len(name) for each name in products". The result is a brand new dict. The original list is untouched.
The shape is always the same: curly braces around a key: value expression, then a for ... in ... clause that names each item.
The colon is what tells Python this is a dict comprehension and not a set comprehension. A set comprehension uses the same braces but has no colon ({x for x in items}). The colon turns the expression into a pair, and pairs are what dicts hold.
Mapping each product to its price-with-tax, given a flat list of prices, is the same pattern:
The for clause unpacks each (name, price) pair coming out of zip. The expression on the left builds the new pair: name as the key, taxed price as the value. zip is the workhorse for building dicts from two parallel sequences, and shows up again below.
A common single-iterable pattern is mapping each number to something derived from it. Imagine a small product registry where each product is identified by an integer id, and you want a lookup table from id to a generated SKU string.
The key is the id itself, and the value is the formatted SKU string. One iterable in, one dict out.
The basic form turns every item into a pair. Often you want to skip some items. Add an if clause at the end of the comprehension, and only items where the condition holds make it into the dict.
Reading order is the same as a list comprehension: the for clause produces each item, the if clause decides whether to keep it, and the key: value expression on the left builds what gets stored.
A common case: from a flat list of (name, stock) pairs, build a dict of only the products that are in stock.
The for clause unpacks each pair, the if clause keeps only the rows where stock is positive, and the expression on the left builds the name: count entry that survives. The output dict is shorter than the input list because the filter dropped two rows.
Filtering an existing dict to a smaller one works the same way, you just iterate .items() to get pairs:
Three of the five entries pass the price < 15 filter, and the other two are dropped. The structure of the entry is unchanged; only the membership of the dict shrinks.
The .items() call gives you (key, value) pairs to iterate over. Treat .items() as the standard way to walk a dict pair by pair.
Filtering keeps a subset of the dict. Transforming changes what each entry holds. The most common transform is keeping the keys exactly as they are and rewriting the values.
The keys stay identical (name on both sides of the original mapping), and the values are transformed. This is the dict version of the list comprehension [x * 1.08 for x in prices], except you keep the labels alongside the numbers.
You can also apply different transforms to different keys by combining a filter and a conditional expression on the value side. A 20% discount only for items priced above $20, everything else unchanged:
The conditional expression round(price * 0.8, 2) if price > 20 else price sits in the value slot. Every entry survives (no if to the right of for), but the value is computed differently depending on the price. This is the same if/else vs if rule that applied to list comprehensions: if/else inside the expression rewrites items, if after the for clause filters them out.
Cost: A dict comprehension is roughly the same speed as the equivalent explicit loop with result[key] = value. The win is readability, not performance. Don't reach for a comprehension expecting a speedup; reach for it when the one-liner reads more clearly than the four-line loop.
Less common, but still useful, is rewriting the keys while keeping the values. Normalizing product names to lowercase before saving them is a typical example.
The keys are lowercased; the values pass through unchanged. The risk with key transforms is that two original keys can collapse to the same new key, which silently drops one of the entries. The same gotcha appears when inverting a dict.
You can transform both sides at once. Maybe you want the keys in lowercase and the prices rounded up to whole dollars:
Both the key and the value pass through their own transform. The shape of the comprehension is unchanged; the work just happens on both sides of the colon.
Inverting a dict means swapping keys and values: every old key becomes a value, and every old value becomes a key. A dict comprehension is the cleanest way to do it.
The trick is in the unpacking. sku_to_name.items() yields (sku, name) pairs, and the expression on the left puts name first (so it becomes the new key) and sku second (so it becomes the new value). Same data, mirrored.
Here's the catch. Dict keys have to be unique. If two entries in the original dict share the same value, those values can't both become keys in the inverted dict, because the second one overwrites the first.
Three of the four entries had "Electronics" as the value. After inversion, "Electronics" becomes a key, and Python keeps only the last assignment, so "HDMI Cable" wins and the first two are silently lost. There's no error, no warning. You just end up with a smaller dict than you might have expected.
This is the single most common bug with dict comprehensions, and it almost always shows up in inversions. The fix depends on what you actually wanted:
defaultdict(list) or a dict.setdefault(...) loop. The result there is a dict like {"Electronics": ["Wireless Mouse", "USB Cable", "HDMI Cable"], "Kitchen": ["Mug"]}, which preserves every original entry.The diagram below shows what's happening internally when the same key gets assigned twice during a comprehension.
Each input pair triggers an assignment into the result dict. When the same key shows up more than once, every assignment after the first one is an overwrite. The final dict only carries the most recent value for each key. Comprehensions don't warn you about this, because at the language level, key reassignment is a normal, well-defined operation. The "loss" is in your data, not in the syntax.
A frequent source of dict-building work is two parallel sequences: one with the keys, one with the values. Names and prices, ids and stock counts, customer names and emails. You want a dict that pairs them up.
You have two ways to do this in Python, and they're both common enough to recognize.
Same result. The question is which one to prefer.
Use dict(zip(keys, values)) when you're just pairing them up with no transformation. It's shorter, idiomatic, and reads as "make a dict out of these zipped pairs". Anyone who's worked with Python for a week will recognize the pattern.
Use the comprehension form when you want to transform a key or value, or filter pairs out, in the same step. The comprehension gives you a place to put the transform; dict(zip(...)) doesn't.
The USB Cable row is dropped by the filter. The remaining rows are transformed before they land in the dict. Trying to do this with dict(zip(...)) would mean a separate loop or a second pass over the result. The comprehension keeps everything in one expression.
One small note on zip: when the two iterables have different lengths, zip stops at the shorter one, silently. That's a normal zip behavior and not specific to comprehensions, but it can show up here as missing entries if the input data isn't aligned.
A dict comprehension is the right tool when you're building a dict from one iterable, with a single transform and maybe a filter, in one step. It's the wrong tool the moment any of those constraints break.
Don't use a comprehension for multi-step transformations. If the key or value depends on intermediate variables, error handling, or calls into separate helpers per item, the comprehension turns into a one-liner nobody enjoys reading. Compare:
The comprehension works, but the value expression has a conditional, a format string, and a tax calculation crammed onto one line. A plain loop with named intermediate steps reads better:
Same result, easier to read, easier to debug. The rule is the same one that applied to list comprehensions: shorter is not better, clearer is better.
Don't use a comprehension just to do side effects. A comprehension that calls print() or writes to a file in the value slot is doing work for its side effects, then throwing away the dict it builds. The intent is muddled and the unused dict is wasted memory. A plain for loop is the right form whenever the goal isn't producing a value.
Don't use a comprehension when you need to accumulate into the same key. If you want to group items by category and end up with {"Electronics": [...], "Kitchen": [...]}, that's not a dict comprehension job. The comprehension treats each pair as a fresh assignment, so repeated keys overwrite rather than accumulate. Use defaultdict(list) or a loop with dict.setdefault(key, []).append(value).
A few more cases where a regular loop wins:
try/except around each step.You can nest a dict comprehension inside another comprehension, and the syntax extends to dicts where the values are themselves dicts (a "dict of dicts"). For example, building a per-category price lookup:
The outer comprehension walks each category. The inner comprehension builds a dict of products in that category. This works, but it's already past the threshold where most readers will pause and re-parse it. Treat this example as a sighting, not a recommendation: when you reach for nested comprehensions in real code, double-check that the equivalent loop wouldn't read better.
Most dict comprehensions you'll write fall into one of these shapes:
| Pattern | Template | Example use |
|---|---|---|
| Build from single iterable | {x: f(x) for x in items} | id-to-SKU lookup |
| Build from zipped iterables | {k: v for k, v in zip(keys, values)} | name-to-price catalog |
| Filter an existing dict | {k: v for k, v in d.items() if cond} | keep in-stock products |
| Transform values | {k: f(v) for k, v in d.items()} | apply tax to prices |
| Transform keys | {f(k): v for k, v in d.items()} | lowercase product names |
| Invert a dict | {v: k for k, v in d.items()} | reverse a SKU mapping |
Whenever you find yourself writing the four-line "empty dict, loop, assignment" version of any of these, the comprehension form is the more idiomatic write-up. Whenever the work doesn't fit one of these shapes cleanly, the loop is the better choice.
10 quizzes