Back to Blog
Python

Python Dictionary Comprehension: Syntax and Use Cases

python dictionary comprehension: Learn how to use Python dictionary comprehension for concise dictionary construction, filtering, transformations, and when a plain loo...

pythondictionarycomprehensionsdata-structurescode-readability
Illustration of Python dictionary comprehension showing key-value pairs generated from an iterable inside curly braces

Python dictionary comprehension gives you a compact way to build dictionaries from iterables in a single expression. The syntax follows the pattern {key_expression: value_expression for item in iterable}. It mirrors list comprehension but produces a dict instead of a list. This article covers the syntax, filtering, transformations, performance characteristics, and the cases where a plain loop is the better choice.

Basic Syntax and Evaluation Order

The simplest form of a dictionary comprehension takes one iterable and produces key-value pairs from each element:

squares = {x: x**2 for x in range(5)} print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

The expression on the left of the colon is evaluated for the key, and the expression on the right is evaluated for the value. Both expressions are re-evaluated for every element in the iterable, so any function calls or attribute lookups inside them run once per item. The iteration order follows the order of the input iterable, which matters when you rely on insertion order in Python 3.7+ where dictionaries preserve insertion order.

You can also iterate over an existing dictionary's items to create a new one:

prices = {"apple": 1.20, "banana": 0.80} with_tax = {item: price * 1.08 for item, price in prices.items()}

Here .items() yields tuples of key-value pairs, which are unpacked into item and price in the comprehension's target expression.

Filtering Items with Conditions

An if clause at the end of the comprehension filters which items are included:

numbers = range(20) even_squares = {n: n**2 for n in numbers if n % 2 == 0}

The condition is evaluated for each element before the key-value pair is constructed. If the condition is false, the element is skipped entirely. This is equivalent to writing a loop with an if statement inside, but it keeps the logic in one line. You can chain multiple if clauses, and they behave like an and condition:

filtered = {n: n**2 for n in range(100) if n > 10 if n % 3 == 0}

While this works, a single combined condition is usually more readable: if n > 10 and n % 3 == 0.

Transforming Keys and Values

Dictionary comprehensions are useful for normalizing data. You can transform keys, values, or both:

raw = {"Alice": "NYC", "Bob": "SF", "Carol": "SEA"} normalized = {name.lower(): city.upper() for name, city in raw.items()}

This pattern is common when preparing data for lookups where case consistency matters. The original dictionary is left unchanged; the comprehension builds a new object. If you need to modify a dictionary in place, a loop with pop or reassignment is more appropriate than a comprehension.

You can also swap keys and values:

original = {"a": 1, "b": 2, "c": 3} inverted = {value: key for key, value in original.items()}

Be aware that if two keys map to the same value, the later one overwrites the earlier one in the inverted dictionary, because duplicate keys are silently replaced.

Building Dictionaries from Other Structures

A common use case is pairing two sequences with zip:

keys = ["name", "age", "city"] values = ["Alice", 30, "NYC"] record = {k: v for k, v in zip(keys, values)}

If the sequences have different lengths, zip stops at the shorter one. If you need to handle mismatched lengths explicitly, itertools.zip_longest gives you control over the fill value.

Counting character frequencies is another frequent pattern:

text = "mississippi" counts = {char: text.count(char) for char in set(text)}

Note that this approach calls text.count once per unique character, which is O(n) per call. For long strings, a loop that increments counts in a single pass is more efficient. The comprehension is fine for short inputs where readability matters more than micro-optimization.

Performance and Memory Characteristics

A dictionary comprehension is generally faster than an equivalent loop that builds a dictionary manually, because the comprehension runs in optimized bytecode that avoids repeated dict.__setitem__ method lookups. The difference is most noticeable when the iterable is large and the key-value expressions are cheap.

Memory is a more important consideration. The comprehension materializes the entire dictionary in memory at once. If you are processing a stream of millions of records and only need a subset, a generator-based approach that yields items one at a time may be better, though you would then need to insert into a dictionary yourself. There is no lazy dictionary comprehension in Python; the {} syntax always builds the complete dict eagerly.

If the key expression is expensive, the cost is paid once per element during construction. There is no way to defer that work, so if the key computation is the bottleneck, consider whether a different data structure or a precomputed key list would help.

Common Mistakes and Edge Cases

The most frequent mistake is using an unhashable type as a key. Lists and dictionaries cannot be keys because they are mutable and unhashable:

# This raises TypeError: unhashable type: 'list' bad = {[1, 2]: "value" for x in range(1)}

Use tuples or strings as keys instead.

Duplicate keys are silently overwritten. If your comprehension produces the same key more than once, only the last value survives. This is often surprising when inverting a dictionary with duplicate values. If you need to preserve all values, collect them into lists:

from collections import defaultdict data = {"a": 1, "b": 1, "c": 2} grouped = defaultdict(list) for key, value in data.items(): grouped[value].append(key)

A comprehension cannot accumulate multiple values per key without extra logic, so a loop with defaultdict is the clearer approach here.

Readability is another concern. A comprehension with multiple nested loops, several conditions, and complex expressions becomes harder to read than an equivalent loop. Python's style guidance favors clarity; if a comprehension spans more than a couple of lines, a loop is often the better choice.

When a Loop Is the Better Choice

Comprehensions are expressions, not statements. They cannot contain print, raise, assert, or other statements. If you need side effects while building the dictionary, use a loop:

result = {} for item in items: if item.valid(): result[item.id] = item.process()

Debugging is also easier with a loop because you can set a breakpoint inside the loop body and inspect intermediate values. Inside a comprehension, you cannot step through the iteration in most debuggers without refactoring.

Exception handling is another limitation. A comprehension has no way to catch exceptions raised by the key or value expressions. If one element raises, the entire comprehension fails. A loop lets you wrap individual iterations in try/except and continue with the remaining items:

result = {} for item in items: try: result[item.id] = item.compute() except ValueError: continue

For straightforward transformations with no side effects, no exception handling, and no need for step-by-step debugging, a dictionary comprehension is the more concise and often faster choice. When any of those conditions change, the loop is the maintainable option.

python dictionary comprehension: Practical Usage and Code Ex | RYUSLOG DEV