Backend / Python core / Tricky questions / 43_inverting_a_dict.md

Inverting a dict — what could go wrong

Updated 3 min read source
On this page6
  1. The gotcha
  2. Minimal repro
  3. How to handle both
  4. Real-world: enum-like lookups
  5. Inversion and order
  6. Interview angle

Inverting a dict — what could go wrong

The gotcha

{v: k for k, v in d.items()} looks innocent. Two failure modes: non-unique values silently drop entries, and non-hashable values raise TypeError.

Minimal repro

Failure mode 1: collisions

python
d = {"a": 1, "b": 2, "c": 1}
inverted = {v: k for k, v in d.items()}
print(inverted)         # {1: 'c', 2: 'b'}    ← 'a' lost

'a' and 'c' both map to 1. The comprehension processes in order, last wins. The mapping 1 → 'a' is silently overwritten.

Failure mode 2: unhashable values

python
d = {"a": [1, 2], "b": [3, 4]}
inverted = {v: k for k, v in d.items()}
# TypeError: unhashable type: 'list'

Values become keys; keys must be hashable. Lists, sets, dicts can’t be keys.

How to handle both

Multi-valued inverse (collisions OK)

python
from collections import defaultdict

d = {"a": 1, "b": 2, "c": 1}
inverted = defaultdict(list)
for k, v in d.items():
    inverted[v].append(k)
print(dict(inverted))   # {1: ['a', 'c'], 2: ['b']}

Now no entries lost. Each key in the inverted dict maps to all originals.

Detect collisions and raise

python
def invert(d):
    out = {}
    for k, v in d.items():
        if v in out:
            raise ValueError(f"non-unique value: {v} appears multiple times")
        out[v] = k
    return out

Use when “every value should be unique” is a real invariant.

Hash unhashable values via tuple/frozenset coercion

python
d = {"a": [1, 2], "b": [3, 4]}
inverted = {tuple(v): k for k, v in d.items()}
# {(1, 2): 'a', (3, 4): 'b'}

Or frozenset if order doesn’t matter.

This loses information (you can’t recover the original list type without remembering it), but for use cases where you only need key_for_value(v) lookup, it works.

Bidict — the dedicated library

python
from bidict import bidict

d = bidict({"a": 1, "b": 2})
d["a"]                  # 1
d.inverse[1]            # "a"

# ValueDuplicationError — raises by default
d["c"] = 1

bidict enforces uniqueness in both directions and gives you inverse for O(1) reverse lookup. Worth pulling in for any non-trivial bidirectional mapping.

Real-world: enum-like lookups

python
HTTP_STATUS = {
    200: "OK",
    301: "Moved Permanently",
    404: "Not Found",
}

# Forward: code → name
HTTP_STATUS[200]                     # "OK"

# Reverse: name → code
NAME_TO_CODE = {v: k for k, v in HTTP_STATUS.items()}
NAME_TO_CODE["Not Found"]            # 404

Safe because HTTP status names are unique by spec. For user-supplied data, use one of the patterns above.

Inversion and order

Python 3.7+ preserves insertion order. The inverted dict’s order reflects the order values were first seen during iteration of the original — which equals original insertion order in single-pass dict comprehension. So:

python
d = {"first": 1, "second": 2, "third": 3}
{v: k for k, v in d.items()}
# {1: 'first', 2: 'second', 3: 'third'}   ← order preserved

When values collide, the later writes don’t change order — they update value at the existing key position.

Interview angle 4

  • Q: “How do you invert a dict?” — {v: k for k, v in d.items()}.
  • Q: “What can go wrong?” — non-unique values silently drop entries; non-hashable values raise.
  • Follow-up: “How would you handle non-unique values?” — defaultdict(list) to accumulate, or raise.
  • Follow-up: “What library is built for bidirectional dicts?” — bidict.

See Duplicate keys in dict comprehensions, collections — specialized containers, __eq__ vs __hash__.