Nested Dictionary Comprehension in Python

⏱️ 2 min read 🐍 Python

A nested dictionary comprehension is simply a dict comprehension whose value expression is another dict comprehension — it lets you build, reshape, or flatten a dict of dicts in a single statement. The pattern is powerful for restructuring data pulled from JSON APIs or grouped records, but it hits a readability wall fast, so knowing when to fall back to a loop matters as much as the syntax.

Quick answer: To build a dict of dicts, put an inner comprehension in the value slot: {k: {ik: iv for ik, iv in inner} for k, inner in data}. To flatten one, use two for clauses in a single comprehension: {(ok, ik): v for ok, inner in d.items() for ik, v in inner.items()}. Outer loop comes first, left to right.

How do I build a dict of dicts with a comprehension?

Nest a second comprehension in the value position of the outer one. The outer comprehension produces the top-level keys; for each of them, the inner comprehension builds a fresh dictionary. This is the go-to pattern for transforming every inner value in one pass — here, converting prices in a nested structure to a different currency.

prices_usd = {
    "fruit": {"apple": 1.20, "cherry": 3.80},
    "veg":   {"carrot": 0.60, "kale": 2.10},
}

RATE = 0.92
prices_eur = {category: {item: round(usd * RATE, 2)
                         for item, usd in items.items()}
              for category, items in prices_usd.items()}

print(prices_eur)
# {'fruit': {'apple': 1.1, 'cherry': 3.5}, 'veg': {'carrot': 0.55, 'kale': 1.93}}

You can also generate a grid from scratch — say, a multiplication table — by nesting over two ranges:

table = {a: {b: a * b for b in range(1, 4)} for a in range(1, 4)}
print(table[2][3])   # 6
print(table)
# {1: {1: 1, 2: 2, 3: 3}, 2: {1: 2, 2: 4, 3: 6}, 3: {1: 3, 2: 6, 3: 9}}

How do I flatten a nested dictionary with a comprehension?

Flattening uses a different shape: one comprehension with two for clauses, not a comprehension inside a comprehension. The clauses read left to right like nested loops — outer first, inner second — and each inner item produces one entry in a single flat dictionary.

prices_usd = {
    "fruit": {"apple": 1.20, "cherry": 3.80},
    "veg":   {"carrot": 0.60, "kale": 2.10},
}

# Option 1: tuple keys
flat = {(cat, item): usd
        for cat, items in prices_usd.items()
        for item, usd in items.items()}
print(flat[("fruit", "apple")])   # 1.2

# Option 2: joined string keys
flat = {f"{cat}.{item}": usd
        for cat, items in prices_usd.items()
        for item, usd in items.items()}
print(flat)
# {'fruit.apple': 1.2, 'fruit.cherry': 3.8, 'veg.carrot': 0.6, 'veg.kale': 2.1}

Getting the clause order backwards is the classic mistake — if you write the inner loop first, you get a NameError because items is not defined yet. When in doubt, write it as a nested for loop first, then copy the loop headers into the comprehension in the same order.

How do I transpose (swap inner and outer keys) a nested dict?

Transposing turns d[a][b] into d[b][a] — for example, flipping {city: {year: population}} into {year: {city: population}}. A nested comprehension handles it when every inner dict shares the same keys: iterate the new outer keys, then rebuild each inner dict by indexing into the original.

pop_by_city = {
    "austin":  {2023: 979, 2024: 986},
    "denver":  {2023: 716, 2024: 720},
}

pop_by_year = {year: {city: data[year]
                      for city, data in pop_by_city.items()}
               for year in next(iter(pop_by_city.values()))}

print(pop_by_year)
# {2023: {'austin': 979, 'denver': 716}, 2024: {'austin': 986, 'denver': 720}}

If the inner dicts have ragged keys (not every city reported every year), the comprehension version raises KeyError or silently drops data depending on how you write it. That is a signal to switch to a loop with setdefault():

pop_by_year = {}
for city, data in pop_by_city.items():
    for year, pop in data.items():
        pop_by_year.setdefault(year, {})[city] = pop

When should I refactor a nested comprehension into a loop?

Refactor as soon as any of these appear: three or more levels of nesting, a filtering if on both levels, error handling needs (missing keys, bad types), or a line you have to horizontally scroll. Comprehensions cannot contain try/except, cannot log progress, and give unhelpful tracebacks — a loop costs three extra lines and buys you all of that back.

# Dense and fragile:
result = {k: {ik: f(iv) for ik, iv in inner.items() if iv is not None}
          for k, inner in raw.items() if inner}

# Same logic, debuggable:
result = {}
for k, inner in raw.items():
    if not inner:
        continue
    cleaned = {}
    for ik, iv in inner.items():
        if iv is not None:
            cleaned[ik] = f(iv)
    result[k] = cleaned

A good rule of thumb: a nested comprehension should express one reshaping idea (transform, flatten, or transpose). The moment it expresses two, split it. For the single-level building blocks these patterns are made of, see dict comprehensions in Python, and if your keys and values need per-item conditional logic, combine these shapes with the techniques in Python dictionary methods such as setdefault() and get().

Pro Tip: The two shapes are easy to confuse: a comprehension inside the value slot builds a dict of dicts, while two for clauses side by side flatten one. If your output should be nested, nest the braces; if it should be flat, chain the fors — and always order chained fors outer-to-inner, exactly as you would write the loops.

← Back to Python Tips