Set & Dictionary Comprehensions
The same comprehension syntax that builds lists extends naturally to sets and dictionaries — swap the brackets, and Python builds a de-duplicated set or a key-value mapping instead.
Set comprehensions ({expression for item in iterable}) and dictionary comprehensions ({key: value for item in iterable}) apply the same for/if comprehension syntax used for lists, but build a set or dict instead. They inherit their container's core properties automatically: a set comprehension de-duplicates results, and a dict comprehension keeps only the last value written for any repeated key.
Set Comprehensions: Syntax
A set comprehension looks just like a list comprehension but with curly braces instead of square brackets: {expression for item in iterable if condition}. Because it builds a set, the result is automatically de-duplicated and unordered — two equal computed values collapse into one element, no matter how many times they'd be produced.
words = ["apple", "banana", "cherry", "avocado", "blueberry"]
first_letters = {w[0] for w in words}
print(first_letters) # {'a', 'b', 'c'} -- order not guaranteed, duplicates gone
squares_mod_5 = {n ** 2 % 5 for n in range(10)}
print(squares_mod_5) # {0, 1, 4} -- far fewer than 10 distinct inputsSet Comprehensions: Filtering
The trailing if filters exactly as it does in a list comprehension, deciding which source items are considered at all before de-duplication happens on whatever survives.
text = "the quick brown fox jumps over the lazy dog"
vowels_used = {ch for ch in text if ch in "aeiou"}
print(vowels_used) # {'e', 'i', 'u', 'o', 'a'} (order varies)Dictionary Comprehensions: Syntax
A dict comprehension uses {key_expression: value_expression for item in iterable if condition} — note the colon separating key from value, the feature that distinguishes it from a set comprehension. It builds a dict mapping each computed key to its corresponding computed value.
words = ["apple", "kiwi", "banana"]
lengths = {w: len(w) for w in words}
print(lengths) # {'apple': 5, 'kiwi': 4, 'banana': 6}
squares = {n: n ** 2 for n in range(5)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}Duplicate Keys: Last Write Wins
If the key expression produces the same key more than once, the dict comprehension keeps only the value from the *last* time that key was produced — exactly the same rule as repeatedly assigning to the same dict key in a loop. Earlier values for a repeated key are silently discarded, so be careful when the key expression isn't guaranteed unique across the source iterable.
words = ["apple", "apricot", "banana", "blueberry"]
by_first_letter = {w[0]: w for w in words}
print(by_first_letter)
# {'a': 'apricot', 'b': 'blueberry'} -- 'apple' and 'banana' were overwrittenTransforming an Existing Dict
A very common use is building a new dict from an existing one — swapping keys and values, filtering entries, or transforming values — by iterating .items().
prices = {"apple": 1.50, "banana": 0.75, "cherry": 4.00}
# filter: only items over $1
expensive = {name: price for name, price in prices.items() if price > 1}
print(expensive) # {'apple': 1.5, 'cherry': 4.0}
# transform values: apply a 10% discount
discounted = {name: round(price * 0.9, 2) for name, price in prices.items()}
print(discounted) # {'apple': 1.35, 'banana': 0.68, 'cherry': 3.6}
# invert: swap keys and values (only safe if original values are unique and hashable)
inverted = {price: name for name, price in prices.items()}
print(inverted) # {1.5: 'apple', 0.75: 'banana', 4.0: 'cherry'}Nested Structures
Dict and set comprehensions support multiple for clauses just like list comprehensions, and their expressions can themselves be comprehensions — useful for building grouped or nested mappings.
groups = {"fruits": ["apple", "banana"], "veggies": ["carrot", "pea"]}
# flatten to a set of all items across every group
all_items = {item for items in groups.values() for item in items}
print(all_items) # {'apple', 'banana', 'carrot', 'pea'}
# build a dict of item -> its group
item_to_group = {item: group for group, items in groups.items() for item in items}
print(item_to_group)
# {'apple': 'fruits', 'banana': 'fruits', 'carrot': 'veggies', 'pea': 'veggies'}Set Comprehension vs set() of a Generator
{expr for x in it} and set(expr for x in it) (a generator expression fed to set()) produce identical results; the comprehension form is generally preferred as more idiomatic and slightly faster (CPython implementation detail: the comprehension avoids the overhead of constructing and consuming a separate generator object). The same relationship holds for dict comprehensions versus dict(...) calls.
nums = [1, 2, 2, 3, 3, 3]
{n for n in nums} == set(n for n in nums) # True -- same result either wayDistinguishing {} for Set vs Dict
A bare {} is an empty *dict*, not an empty set — there is no comprehension-free literal for an empty set (you must call set()). This ambiguity resolves inside a comprehension by the presence of a colon: {x for x in it} is a set comprehension (no colon), {k: v for x in it} is a dict comprehension (colon present).
empty_dict = {}
type(empty_dict) # <class 'dict'>
empty_set = set()
type(empty_set) # <class 'set'>
{x for x in []} # a valid (empty) set comprehension -- has no colonPractical Example: Word Frequency and Uniqueness
A set comprehension quickly finds the distinct vocabulary; a dict comprehension filters a Counter's items down to only the words that repeat.
text = "the quick brown fox the lazy fox the dog"
words = text.split()
unique_words = {w for w in words}
print(len(unique_words), "unique words out of", len(words))
from collections import Counter
counts_over_1 = {w: c for w, c in Counter(words).items() if c > 1}
print(counts_over_1) # {'the': 3, 'fox': 2}Key Takeaways
{expr for x in it} builds a de-duplicated, unordered set; {k: v for x in it} builds a dict — the colon is what distinguishes them.
Both support the same for/if filtering and multi-clause nesting as list comprehensions.
A dict comprehension with a repeated key keeps only the last value written for that key — earlier ones are silently discarded.
Iterating .items() over an existing dict is the standard way to filter or transform it into a new dict via comprehension.
A bare {} is an empty dict, never an empty set — use set() for that.
empty = {} # intending an empty set {} always creates an empty dict, never an empty set -- there is no bare literal for an empty set, since {} was already claimed by dict before set comprehensions/literals existed.
by_id = {user["dept"]: user for user in users} # dept is not unique across users by_dept = {} for user in users: by_dept.setdefault(user["dept"], []).append(user)
A dict comprehension silently keeps only the last value for a repeated key; if the key expression isn't actually unique across the source data, earlier entries vanish without warning. Group into lists instead when keys can repeat.
inverted = {v: k for k, v in original.items()} # original's values aren't unique
Inverting a dict via comprehension only works cleanly when the original values are unique and hashable; if two keys share a value, the inversion silently drops one of them, the same last-write-wins issue as any duplicate-key dict comprehension.
What is the syntactic difference between a set comprehension and a dict comprehension?
A dict comprehension has a colon separating a key expression from a value expression: {key: value for ...}. A set comprehension has a single expression with no colon: {expression for ...}.
Given words = ['a', 'bb', 'ccc', 'dd'], what does {len(w) for w in words} produce?
{1, 2, 3} -- the lengths are 1, 2, 3, 2, but since it's a set comprehension, the duplicate length 2 (from 'bb' and 'dd') collapses into a single element.
If a dict comprehension's key expression produces the same key twice with different values, which value ends up in the result?
The value from the last time that key was produced, in iteration order -- earlier values for the same key are overwritten and discarded, identical to what happens when you repeatedly assign to the same dict key in an explicit loop.
How would you build an empty set using literal/comprehension syntax versus using {}?
{} always creates an empty dict. To create an empty set you must call the set() constructor -- there is no bare-braces literal for an empty set. (A non-empty set comprehension like {x for x in []} is valid and produces an empty set, but a bare {} does not.)
Write a dict comprehension that inverts a dict d (making values keys and keys values), and state the precondition required for this to be safe.
{v: k for k, v in d.items()} -- this is only safe (loses no information) when d's values are unique and hashable; if two keys share a value, one mapping is silently overwritten by the last-write-wins rule.
Why might {n for n in numbers} be preferred over set(n for n in numbers) even though they produce the same result?
The comprehension form is generally considered more idiomatic/readable, and as a CPython implementation detail it's typically a bit faster since it avoids constructing and then consuming a separate generator object -- though both are correct and equivalent.
Given groups = {'a': [1, 2], 'b': [3]}, what does {x for values in groups.values() for x in values} produce?
{1, 2, 3} -- the outer for clause iterates the lists in groups.values(), the inner for clause iterates each number within those lists, and the set comprehension collects and de-duplicates all of them.
Write a dict comprehension that maps each word in a list to its length, but only for words longer than 3 characters.
words = ["hi", "hello", "a", "world", "ok"] result = {w: len(w) for w in words if len(w) > 3} print(result) # {'hello': 5, 'world': 5}
The dict comprehension {w: len(w) for w in words if len(w) > 3} filters out short words with the trailing if before computing the key:value pair for the ones that remain.
What does this code print?
It prints {10, 20} then 2. Even though prices has 3 entries, two of them share the value 10, and a set comprehension de-duplicates -- so the resulting set has only 2 distinct elements.
Write a function group_by_parity(numbers) that returns a dict with keys 'even' and 'odd', each mapping to a set of the numbers in the input with that parity, using set/dict comprehension-friendly logic.
def group_by_parity(numbers: list[int]) -> dict[str, set[int]]: return { "even": {n for n in numbers if n % 2 == 0}, "odd": {n for n in numbers if n % 2 == 1}, } print(group_by_parity([1, 2, 2, 3, 4, 4, 5])) # {'even': {2, 4}, 'odd': {1, 3, 5}}
Two separate set comprehensions, each filtering numbers by parity with the trailing if, are combined into a dict literal with fixed keys 'even' and 'odd' -- each value is independently de-duplicated by virtue of being a set.
What does this print, and why does the result have fewer entries than d?
It prints {1: 'z', 2: 'y'} -- only 2 entries, not 3. Because 'x' and 'z' both map to the value 1 in d, inverting produces the key 1 twice; the dict comprehension's last-write-wins rule means the pairing from 'z' (processed after 'x' in insertion order) overwrites the one from 'x'.
prices = {"a": 10, "b": 20, "c": 10}
result = {price for price in prices.values()}
print(result)
print(len(result))d = {"x": 1, "y": 2, "z": 1}
inverted = {v: k for k, v in d.items()}
print(inverted)