A Python dict comprehension builds a whole dictionary in a single line, and this post pairs it with nested dictionaries through tested examples. Learn to build, filter, and transform dicts in one line, plus patterns for working with deeply nested data structures.
“Talk is cheap. Show me the code.”
Linus Torvalds
Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Beginner | Reading Time: 15 minutes
You catch yourself writing the same three lines over and over: make an empty dict, start a loop, then add one key-value pair on each pass. It works, but it feels like a lot of typing for something so simple. A Python dictionary comprehension collapses all of that into one expression: {key_expr: value_expr for item in iterable}. You describe the mapping you want, and Python builds the whole dict for you.
Think of it like filling out a class attendance sheet. The slow way is to write each name and tick each box by hand, one row at a time. The comprehension is the printed template that already lays out every name next to every box in a single pass. Same result, far less hand cramp.
If you have met list comprehensions, this is the same idea with a colon added. A list comprehension builds a list. A dict comprehension builds a dict, and the colon is what tells Python which part is the key and which part is the value. By the end of this post you will build, filter, transform, and invert dicts in one line, work through nested dicts (the kind you get back from APIs, short for Application Programming Interfaces), and know when a comprehension actually helps versus when it just hides the logic.
Table of Contents
The Problem: Building Dicts the Long Way
Say you have a list holding the names of four friends, Rahul, Niranjan, Viraj, and Pravin, and you want a dict that maps each name to its length. The straightforward approach is the create-loop-assign dance: start with an empty dict, walk the list, and assign one key on every pass.
📄 old_way.py: loop and assign pattern
names = ["Rahul", "Niranjan", "Viraj", "Pravin"]
name_lengths = {}
for name in names:
name_lengths[name] = len(name)
print(name_lengths)
▶ Output
{'Rahul': 5, 'Niranjan': 8, 'Viraj': 5, 'Pravin': 6}
The Solution: Dict Comprehension Syntax
Here is that same dict in one line. A comprehension works like a spreadsheet formula: instead of typing a value into every cell by hand, you write the rule once and it fills the whole column. Alongside it are two more patterns you will reach for constantly: building a squares lookup, and zipping two parallel lists into a dict, here a profile for a user named Aditi.
📄 dict_comp.py: the one-liner version
names = ["Rahul", "Niranjan", "Viraj", "Pravin"]
name_lengths = {name: len(name) for name in names}
print(name_lengths)
# Squares dict
squares = {n: n ** 2 for n in range(1, 8)}
print(squares)
# From two parallel lists using zip
keys = ["name", "age", "city"]
values = ["Aditi", 25, "Pune"]
profile = {k: v for k, v in zip(keys, values)}
print(profile)
▶ Output
{'Rahul': 5, 'Niranjan': 8, 'Viraj': 5, 'Pravin': 6}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49}
{'name': 'Aditi', 'age': 25, 'city': 'Pune'}
What happened here: The shape is always {key_expr: value_expr for item in iterable}. Each trip through the loop produces exactly one key-value pair, and the colon is the dividing line: whatever sits to its left becomes the key, whatever sits to its right becomes the value. That zip() trick on the last example pairs up two lists by position (first with first, second with second), and you will see it merging parallel lists into a dict all over real codebases.
Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.
The diagram reads top to bottom and labels every piece of a dictionary comprehension: the opening brace, the key expression, the colon, the value expression, then the for loop that drives it, and an optional if filter at the end. Python works through the iterable, keeps only the items that pass the filter, and builds one key-value pair for each survivor. That single line replaces the whole empty-dict-then-loop-then-assign pattern you saw a moment ago.
Filtering with Conditions
Add an if at the end and the comprehension turns into a bouncer at the door: only the pairs that pass the test make it into the new dict. Here we take five students’ marks, keep only those who scored 80 or above, then pull out every config key that starts with db_.
📄 filtering.py: add an if clause
scores = {"Rahul": 95, "Niranjan": 88, "Viraj": 72, "Pravin": 61, "Anvi": 90}
# Keep only passing scores
passing = {name: score for name, score in scores.items() if score >= 80}
print(f"Passing: {passing}")
# Filter by key pattern
config = {"db_host": "localhost", "db_port": 5432, "api_key": "secret", "db_name": "myapp"}
db_config = {k: v for k, v in config.items() if k.startswith("db_")}
print(f"DB config: {db_config}")
▶ Output
Passing: {'Rahul': 95, 'Niranjan': 88, 'Anvi': 90}
DB config: {'db_host': 'localhost', 'db_port': 5432, 'db_name': 'myapp'}
What happened here: The if clause sits after the for clause and runs on every item. When it is true the pair gets added, when it is false the pair is skipped. Filtering on the value (score >= 80) and filtering on the key (k.startswith("db_")) both work, because by the time the condition runs you already have both k and v in hand.
Transforming Keys and Values
The key and value are just expressions, so you can run them through any function or bit of math. Think of a currency exchange counter: every note you slide across gets converted by the same fixed rule, and you walk away with a new stack while the original stays untouched. Drop prices by 10 percent, upper-case the keys, convert a city temperature table from Celsius to Fahrenheit: same one-line shape every time.
📄 transform.py: modify keys, values, or both
prices = {"apple": 1.50, "banana": 0.75, "mango": 2.25}
# Apply discount to values
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(f"10% off: {discounted}")
# Uppercase keys
upper = {k.upper(): v for k, v in prices.items()}
print(f"Upper keys: {upper}")
# Convert Celsius to Fahrenheit
celsius = {"Mumbai": 32, "Delhi": 38, "Pune": 28, "Bangalore": 24}
fahrenheit = {city: round(c * 9/5 + 32, 1) for city, c in celsius.items()}
print(f"Fahrenheit: {fahrenheit}")
▶ Output
10% off: {'apple': 1.35, 'banana': 0.68, 'mango': 2.02}
Upper keys: {'APPLE': 1.5, 'BANANA': 0.75, 'MANGO': 2.25}
Fahrenheit: {'Mumbai': 89.6, 'Delhi': 100.4, 'Pune': 82.4, 'Bangalore': 75.2}
What happened here: Notice the mango: 2.25 * 0.9 is 2.025 in pure math, yet Python prints 2.02, not 2.03. That is not a bug in the comprehension. Floats cannot store 2.025 exactly, so the value Python actually holds is a hair under it, and round() dutifully rounds that down. The comprehension just did the math you asked for. If you need exact money math, reach for the decimal module instead of floats, but the one-line transform pattern stays the same.
Inverting a Dictionary
Sometimes you have a name-to-role lookup and you want a role-to-name lookup instead. Flipping keys and values is a two-token change: write v: k where you would normally write k: v. There is one trap, and the second example walks straight into it on purpose.
📄 invert.py: swap keys and values
roles = {"Rahul": "Backend", "Viraj": "Frontend", "Niranjan": "ML"}
inverted = {v: k for k, v in roles.items()}
print(f"Inverted: {inverted}")
# Careful: if values aren't unique, you lose data
grades = {"Rahul": "A", "Viraj": "B", "Anvay": "A"}
inverted = {v: k for k, v in grades.items()}
print(f"Lost data: {inverted}") # Only one "A" key survives
▶ Output
Inverted: {'Backend': 'Rahul', 'Frontend': 'Viraj', 'ML': 'Niranjan'}
Lost data: {'A': 'Anvay', 'B': 'Viraj'}
What happened here: Inverting is clean when every value is unique. When two people share a value, you have a problem: a dict cannot hold the same key twice, so the last one written wins and the earlier one vanishes without a word. Two students, Rahul and Anvay, both had grade "A", and only Anvay survived because his pair was assigned last. It is like two people trying to save their number under the same contact name on one phone; the second save quietly replaces the first.
If you need to keep everyone, invert into lists instead: {v: [k for k, val in d.items() if val == v] for v in set(d.values())}, which groups all the matching keys under each value.
Working with Nested Dictionaries
A nested dictionary is just a dict whose values are themselves dicts. Picture a filing cabinet: the outer dict is the cabinet, each drawer is a person, and inside each drawer are folders for role, level, and skills. You will meet this shape constantly because it is exactly what JSON (JavaScript Object Notation) from an API looks like. The team dict below tracks three teammates, Rahul, Viraj, and Aviraj, one drawer each. Comprehensions handle it well as long as you remember that info below is a whole inner dict, so you reach into it with info["role"].
📄 nested_patterns.py: common nested dict operations
team = {
"Rahul": {"role": "Backend", "level": 3, "skills": ["Python", "PostgreSQL"]},
"Viraj": {"role": "Frontend", "level": 2, "skills": ["React", "TypeScript"]},
"Aviraj": {"role": "DevOps", "level": 3, "skills": ["Docker", "K8s"]},
}
# Extract one field from nested dicts
roles = {name: info["role"] for name, info in team.items()}
print(f"Roles: {roles}")
# Filter by nested value
seniors = {name: info for name, info in team.items() if info["level"] >= 3}
print(f"Seniors: {list(seniors.keys())}")
# Count skills per person
skill_counts = {name: len(info["skills"]) for name, info in team.items()}
print(f"Skill counts: {skill_counts}")
▶ Output
Roles: {'Rahul': 'Backend', 'Viraj': 'Frontend', 'Aviraj': 'DevOps'}
Seniors: ['Rahul', 'Aviraj']
Skill counts: {'Rahul': 2, 'Viraj': 2, 'Aviraj': 2}
What happened here: All three comprehensions loop over the same team.items(), where name is the outer key and info is the inner dict. The first pulls one field out of each inner dict, the second keeps the whole inner dict but only for level 3 and up, and the third counts the items in each skills list. The outer loop stays flat; you just dig one level deeper inside the value expression.
Flattening Nested Data
Flattening is the reverse move: take a nested dict and squash it into one level by joining the outer and inner keys into a single key like user_name. It is like emptying a suitcase packed with labelled pouches into one flat drawer: you tag every item with the pouch it came from, so nothing loses its context. This is handy when you want to dump an API response into a CSV (Comma-Separated Values) row or a flat config. The response below is a profile for a user named Prathamesh. The plain loop version reads clearly; the comprehension version below it does the same job in one line with two for clauses.
📄 flatten.py: turn nested dicts into flat records
# API-style nested response
response = {
"user": {"name": "Prathamesh", "age": 24},
"settings": {"theme": "dark", "lang": "en"},
}
# Flatten with prefix
flat = {}
for section, data in response.items():
for key, value in data.items():
flat[f"{section}_{key}"] = value
print(f"Flat: {flat}")
# Or as a comprehension (less readable but compact)
flat2 = {f"{s}_{k}": v for s, d in response.items() for k, v in d.items()}
print(f"Flat2: {flat2}")
▶ Output
Flat: {'user_name': 'Prathamesh', 'user_age': 24, 'settings_theme': 'dark', 'settings_lang': 'en'}
Flat2: {'user_name': 'Prathamesh', 'user_age': 24, 'settings_theme': 'dark', 'settings_lang': 'en'}
What happened here: Both versions produce the identical dict. In the comprehension, the two for clauses read left to right just like nested loops: for s, d in response.items() is the outer loop, and for k, v in d.items() is the inner one. The key expression f"{s}_{k}" glues the section name and the inner key together. Honestly, the plain loop is the easier one to read here, and that is a fine reason to keep it.
When NOT to Use Dict Comprehensions
A comprehension is a one-liner, not a contest to see how much you can cram into that line. The rule is the same one you learned for list comprehensions: if a human cannot read the logic in a single glance, write the loop. The flatten example above is the warning sign. Once you stack two for clauses, add a condition or two, and the line creeps past 80 or 90 characters, you have written code that only you can read today and nobody (including you) can read next month. When that happens, unpack it into a regular loop. Clear beats clever.
Common Mistakes
Mistake 1: Writing a set comprehension when you meant a dict
The curly braces are shared between sets and dicts, so the colon is the only thing that tells them apart. Forget the colon and you silently get a set, not a dict.
🚫 Missing the value expression creates a set
# This is a SET, not a dict
result = {x for x in range(5)}
print(type(result)) # <class 'set'>
# This is a DICT
result = {x: x**2 for x in range(5)}
print(type(result)) # <class 'dict'>
Mistake 2: Duplicate keys silently overwrite
If your source data has the same key more than once, the comprehension does not warn you or raise an error. The last pair simply wins and the earlier one is gone, the same overwrite trap you saw when inverting.
🚫 Last value wins
items = [("a", 1), ("b", 2), ("a", 3)]
result = {k: v for k, v in items}
print(result) # {'a': 3, 'b': 2}, the first 'a' is gone
Best Practices
- DO use
zip(keys, values)to create dicts from parallel lists - DO use dict comprehensions for simple transforms and filters
- DO watch out for silent key overwrites when inverting
- DON’T nest more than one
forclause in a dict comprehension - DON’T confuse
{x for ...}(set) with{x: y for ...}(dict)
Conclusion
Dictionary comprehensions follow the same shape as list comprehensions, just with a colon: {key: value for item in iterable if condition}. They are the go-to tool for transforming, filtering, and building dicts from other iterables. Pair them with zip() for parallel lists and .items() for existing dicts and you have covered the large majority of everyday dict-building. Nested dicts show up everywhere in JSON and API responses, so reach into them inside the value expression, and flatten them only when the depth starts getting in your way. The one rule to carry forward: if the line stops being readable, go back to a plain loop.
Next up: Sets, collections built around uniqueness and fast membership testing. And if you want to jump ahead, revisit an earlier topic, or see where this series is headed, browse the full Python + AI/ML tutorial series home.
Practice Exercises
- Exercise 1: Map numbers 1-10 to squares with a dict comprehension.
- Exercise 2: Invert a dictionary. Handle duplicate values.
- Exercise 3: Group words by first letter using dict comprehension.
Frequently Asked Questions
What is a dictionary comprehension in Python?
A one-line syntax for creating dictionaries: {key: value for item in iterable}. It replaces the create-loop-assign pattern. Example: {n: n**2 for n in range(5)} produces {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}.
How do I filter a dictionary in Python?
Use a dict comprehension with an if clause: {k: v for k, v in d.items() if v > threshold}. This creates a new dict containing only items that pass the condition.
How do I invert a dictionary (swap keys and values)?
Use {v: k for k, v in d.items()}. Warning: if values aren’t unique, only the last key per duplicate value survives. For safe inversion, collect keys into lists.
What is the difference between a set and dict comprehension?
{x for x in items} creates a set (no colon). {x: y for x in items} creates a dict (has a colon separating key and value). The curly braces alone are ambiguous, so the colon is what decides which one you get.
How do I create a dict from two lists in Python?
Use dict(zip(keys, values)) or a comprehension: {k: v for k, v in zip(keys, values)}. Both pair up elements by position. If lists have different lengths, zip stops at the shortest.
Interview Questions on Dict Comprehensions
These come from real screens and onsites. Practice answering before you read each answer.
Q: Is a dict comprehension faster than an equivalent for loop?
Usually a little, because the loop runs as specialized bytecode without a repeated attribute lookup and method call on every pass. For typical data sizes the gap is small, so the honest answer is that clarity, not speed, is the main reason to use one. If performance actually matters for your case, measure both versions with timeit instead of assuming.
Q: Can you use if-else inside a dict comprehension?
Yes, but position matters. A conditional expression in the value part goes before the for: {name: ("pass" if score >= 80 else "fail") for name, score in scores.items()} keeps every student and computes a different value per pair. The trailing if after the for is a filter that decides whether a pair gets in at all, and it cannot take an else; writing one there is a SyntaxError.
Q: What can and cannot be used as a key in a dict comprehension?
The same rule as any dict: keys must be hashable, so strings, numbers, booleans, and tuples of immutables all work. If your key expression produces a list, set, or dict, Python raises TypeError: unhashable type at runtime, not at parse time. When you need a compound key like city plus year, use a tuple: {(city, year): temp for ...}.
Q: You build a dict from 10,000 CSV rows keyed by customer ID, but the result has only 9,400 entries. What happened?
Some customer IDs repeat in the file. A dict cannot hold the same key twice, so each duplicate silently overwrites the earlier pair and the last row wins; the comprehension never warns you. Confirm it by comparing len(rows) with len(set(ids)), then decide whether last-wins is acceptable or whether you should group the rows into a list per ID instead.
Q: A reviewer flags your one-line comprehension with two for clauses and an if as unreadable. What do you do?
Rewrite it as a plain nested loop. A comprehension earns its place only while the logic fits in a single glance, and two for clauses plus a condition is past that line for most readers. The loop does essentially the same work at runtime, and the next person who maintains the code, including future you, will read it correctly on the first try.
Q: You run a dict comprehension over every line of a 2 GB log file and memory usage spikes. What do you check first?
First, remember that a comprehension materializes the entire result in memory at once, so a dict with one entry per log line can easily be huge. Check whether you truly need all pairs at the same time; if you only need an aggregate like counts per user, loop over the file line by line and update a small running dict instead. Also make sure you iterate the file object directly rather than calling readlines(), which loads the whole file into a list before the comprehension even starts.
Go deeper: when you outgrow this post, the official Python documentation is the next stop.
Related Posts
Previous: Python: Dictionaries, CRUD, Methods, and When to Use
Next: Python Sets: Operations, Math Sets, frozenset
Series Home: Python + AI/ML Tutorial Series

No comment