DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
Getting Started
01
What is Python and Why Learn It in 2026
02
Installing Python on Windows, Mac, and Linux
03
Setting up VS Code / PyCharm for Python
04
Understanding the Python Interpreter and REPL
05
Writing and Running Your First Python Program
06
Python Syntax and Indentation Rules
Variables and Data Types
01
Variables and Naming Conventions in Python
02
Numbers in Python (int, float, complex)
03
Strings and String Methods in Python
04
Booleans in Python
05
Type Conversion / Casting in Python
06
Understanding the None Type in Python
Operators
01
Arithmetic Operators in Python
02
Comparison Operators in Python
03
Logical Operators in Python
04
Assignment Operators in Python
05
Bitwise Operators in Python
06
Identity (is) and Membership (in) Operators
07
Walrus Operator (:=) in Python
Input, Output & Strings
01
input() and print() Functions in Python
02
f-strings and String Formatting in Python
03
String Slicing and Indexing in Python
04
Common String Methods (split, join, strip, replace)
05
Escape Characters in Python Strings
Control Flow
01
if, elif, else Statements in Python
02
Nested Conditionals in Python
03
for Loops in Python
04
while Loops in Python
05
break, continue, and pass Statements
06
Loop with else Clause in Python
07
match-case (Structural Pattern Matching)
Data Structures
01
Python Lists: Creation, Indexing, Slicing, Methods
02
Python Tuples and Immutability
03
Python Sets and Set Operations
04
Python Dictionaries: Keys, Values, and Methods
05
List Comprehensions in Python
06
Dictionary & Set Comprehensions in Python
07
Working with Nested Lists and Dictionaries
Functions
01
Defining and Calling Functions in Python
02
Function Arguments: Positional, Keyword, Default
03
*args and **kwargs in Python
04
Return Statements in Python Functions
05
Lambda Functions in Python
06
Recursion in Python
07
Variable Scope: Local, Global, Nonlocal
08
Docstrings and Function Annotations
Functional Programming Basics
01
map(), filter(), and reduce() in Python
02
Closures in Python Explained
03
Python Decorators: Basic to Advanced
04
Generators and yield in Python
Object-Oriented Programming (OOP)
01
Classes and Objects in Python
02
The init Constructor in Python
03
Instance vs Class Variables in Python
04
Inheritance in Python (Single, Multiple, Multilevel)
05
Polymorphism in Python
06
Encapsulation in Python
07
Abstraction in Python with the abc Module
08
Magic/Dunder Methods in Python
09
Static and Class Methods in Python
10
The @property Decorator in Python
11
Python Dataclasses Explained
Error Handling
01
try, except, finally in Python
02
Handling Multiple Exceptions in Python
03
Raising Custom Exceptions in Python
04
Understanding Python's Exception Hierarchy
05
Assertions in Python
File Handling
01
Reading and Writing Text Files in Python
02
Working with the with Statement in Python
03
Working with CSV Files in Python
04
Working with JSON Files in Python
05
Working with Directories (os, pathlib) in Python
Modules and Packages
01
Importing Built-in Modules in Python
02
Creating Your Own Modules in Python
03
Understanding Packages and init.py
04
Python Standard Library Overview
05
Installing Packages with pip
Advanced Core Concepts
01
Iterators and Iterables in Python
02
Context Managers in Python (with, contextlib)
03
Multithreading Basics in Python
04
Multiprocessing Basics in Python
05
Asyncio Basics: async/await in Python
06
Memory Management and Garbage Collection in Python
07
Type Hints in Python (typing module)

Dictionary & Set Comprehensions in Python

The earlier article on list comprehensions briefly touched on their dict and set counterparts — this one goes deeper into python dictionary comprehension and python set comprehension syntax specifically, including the bugs that show up when the two get confused with each other, or when a filtering condition ends up in the wrong spot.

Introduction: Extending Comprehension Syntax to Dicts and Sets

List comprehension syntax extends naturally to two other collection types, using the same curly braces you already know from dictionary and set literals.

Dict comprehension syntax
{key_expr: value_expr for item in iterable}

The canonical first example — numbers mapped to their squares:

squares = {n: n ** 2 for n in range(5)}
print(squares)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Set comprehension syntax
{expr for item in iterable}
unique_lengths = {len(word) for word in ["cat", "dog", "fish", "ox"]}
print(unique_lengths)   # {3, 4, 2}

Notice the structural difference between the two: a dict comprehension has a colon separating a key expression from a value expression; a set comprehension has just a single expression, no colon at all. Same braces, genuinely different meaning — a distinction worth internalizing early, since it's easy to miss at a glance.

Dictionary Comprehensions in Practice

Building dicts from zip()

zip() pairs up corresponding items from two sequences, and combining it with a dict comprehension is a clean way to build a dictionary from two related lists:

states = ["California", "Texas", "New York"]
capitals = ["Sacramento", "Austin", "Albany"]

state_capitals = {state: capital for state, capital in zip(states, capitals)}
print(state_capitals)
# {'California': 'Sacramento', 'Texas': 'Austin', 'New York': 'Albany'}
Building dicts from .items()

Transforming an existing dictionary — say, applying some operation to every value — typically means iterating its .items():

prices = {"apple": 1.50, "banana": 0.75, "cherry": 4.00}

discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted)
# {'apple': 1.35, 'banana': 0.68, 'cherry': 3.6}
Filtering vs. conditional values — a common source of bugs

This distinction was covered for list comprehensions in the earlier article, and it applies identically here, with the same potential for confusion:

Filtering with a trailing if — excludes items entirely from the result:

prices = {"apple": 1.50, "banana": 0.75, "cherry": 4.00}

expensive_ {item: price for item, price in prices.items() if price > 1.00}
print(expensive_only)   # {'apple': 1.5, 'cherry': 4.0} — banana is gone entirely

A conditional value expression — keeps every item, but changes the value based on a condition:

labeled = {item: ("expensive" if price > 1.00 else "cheap") for item, price in prices.items()}
print(labeled)
# {'apple': 'expensive', 'banana': 'cheap', 'cherry': 'expensive'} — every item still present

Mixing these up is a genuinely common bug: writing a filtering if when you meant a conditional value (accidentally dropping items you wanted to keep, just relabeled), or vice versa (keeping items you meant to exclude). Whenever you're debugging a comprehension that has the wrong number of items in the result, this distinction is the first thing worth checking.

Practical pattern: inverting a dictionary
original = {"a": 1, "b": 2, "c": 3}
inverted = {value: key for key, value in original.items()}
print(inverted)   # {1: 'a', 2: 'b', 3: 'c'}

The caveat: this only produces a correct, complete result if the original dictionary's values are themselves unique and hashable. If two keys share the same value, the inverted dictionary will silently lose one of them — whichever key was processed last simply overwrites the earlier one at that shared value's position, with no warning.

Practical pattern: building a lookup table from records
users = [
    {"id": 101, "name": "Alex"},
    {"id": 102, "name": "Sam"},
    {"id": 103, "name": "Jordan"},
]

lookup = {user["id"]: user["name"] for user in users}
print(lookup)   # {101: 'Alex', 102: 'Sam', 103: 'Jordan'}

This is an extremely common real-world pattern — converting a list of records (the kind of shape you'd get back from a database query or an API response) into a fast, ID-keyed lookup dictionary.

Practical pattern: removing sensitive keys from a payload
user_data = {"username": "alex99", "email": "[email protected]", "password": "secret123", "age": 30}

sensitive_keys = {"password"}
safe_data = {key: value for key, value in user_data.items() if key not in sensitive_keys}
print(safe_data)
# {'username': 'alex99', 'email': '[email protected]', 'age': 30}

A genuinely practical use case — stripping sensitive fields before logging or displaying a dictionary that came from user input or a database record.

Set Comprehensions in Practice

Core use case: unique values from a sequence
words = ["apple", "banana", "avocado", "cherry", "blueberry"]

first_letters = {word[0] for word in words}
print(first_letters)   # {'a', 'b', 'c'}
dice_rolls = [3, 5, 2, 3, 6, 5, 1, 3]
unique_rolls = {roll for roll in dice_rolls}
print(unique_rolls)   # {1, 2, 3, 5, 6}

Both examples follow the same shape: transform (or simply pass through) every item from a sequence, and let the set's automatic deduplication collapse any repeats.

Combining with a filter condition
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

unique_even_squares = {n ** 2 for n in numbers if n % 2 == 0}
print(unique_even_squares)   # {64, 4, 36, 100, 16}
Contrast with dict comprehensions

Because both dict and set comprehensions use the exact same curly-brace syntax, they're easy to confuse at a glance — the only structural difference is the presence or absence of a colon:

# Dict comprehension — has a colon
{n: n ** 2 for n in range(5)}

# Set comprehension — no colon
{n ** 2 for n in range(5)}

When skimming code quickly, it's worth deliberately checking for that colon before assuming which type of comprehension you're looking at — a habit that pays off the first time you're debugging something and initially misread one for the other.

Nested Comprehensions and Common Pitfalls

Shallow nesting for grid-like structures

A nested dict comprehension can build a grid-shaped structure in a single expression — a multiplication table is the classic example:

multiplicati {
    i: {j: i * j for j in range(1, 6)}
    for i in range(1, 6)
}
print(multiplication_table[3])   # {1: 3, 2: 6, 3: 9, 4: 12, 5: 15}

This builds a dictionary of dictionaries — for each i, an inner dict comprehension builds all its multiples. This one level of nesting is generally the practical limit worth reaching for directly; anything deeper tends to become genuinely difficult to read at a glance, and is usually better expressed as a plain nested loop with clear, named intermediate variables.

Common bug: duplicate keys silently overwriting
records = [("apple", 1), ("banana", 2), ("apple", 3)]

result = {name: count for name, count in records}
print(result)   # {'apple': 3, 'banana': 2} — the first "apple" entry is just... gone

There's no error here, no warning — the second "apple" entry simply overwrote the first, since dictionary keys must be unique. If your source data might contain duplicate keys and you actually need to keep every value, a dict comprehension is the wrong tool — you'd want to build a dict of lists instead, typically using .setdefault() in a regular loop, as covered in the earlier dictionaries article.

Common bug: {} is not an empty set
empty = {}
print(type(empty))   # <class 'dict'> — not a set!

empty_set = set()   # this is the correct way to create an empty set

This is the same gotcha covered in the earlier sets article, but worth repeating here specifically because it also affects comprehensions — if a set comprehension's iterable happens to be empty, the comprehension itself still correctly produces an empty set (since the {...} here has comprehension syntax inside it, not a bare literal), but writing {} directly, with nothing else inside, always means an empty dict.

Common bug: if in the wrong position, quietly changing behavior

As covered in Section 2, mixing up a filtering if with a conditional-value if/else doesn't raise an error — it just quietly produces a differently-shaped result than intended, which makes it a genuinely sneaky class of bug to track down after the fact.

Debugging tip

If a dict comprehension's result has fewer entries than you expected, compare the length of your source data against the number of unique keys it would actually produce:

records = [("apple", 1), ("banana", 2), ("apple", 3)]

print(len(records))                          # 3
print(len({name for name, _ in records}))    # 2 — fewer unique keys than source records!

If those two numbers don't match, duplicate keys are silently collapsing entries in your comprehension — exactly the scenario from the earlier example.

When to Use Comprehensions vs. a Loop

Good fit

Comprehensions genuinely shine for transformations and filters that read clearly in one or two lines: discounting a dictionary of prices, grouping words by their first letter, deduplicating a list of values into a set. In all of these, the logic is simple enough that the comprehension is arguably more readable than the equivalent loop, not less.

Reach for a regular loop instead when...
  • The logic needs multiple distinct steps that don't collapse cleanly into one expression.

  • You need error handling (try/except) around individual items.

  • Nesting would go beyond a single level, as covered above.

  • You're relying on side effects (printing, writing to a file, modifying something outside the comprehension) rather than actually building a new collection.

Every comprehension has an equivalent loop

Worth remembering as a general principle: comprehensions are a readability and conciseness tool, not a fundamentally different capability from a loop. Anything you can write as a dict or set comprehension, you can also write as a regular loop building up the same result — the comprehension is just a more compact way to express the same logic, when that logic is simple enough to fit cleanly into one line. If it stops fitting cleanly, there's no penalty for falling back to the loop version — it's not a worse solution, just a more explicit one.

PREVIOUSNEXT LESSON