Dictionaries Interview Questions & Answers

6 questions Updated 2026-06-18

Python interview questions on dict insertion ordering, get vs bracket access, setdefault, merging dicts, view objects, why keys must be hashable, and dict comprehensions.

Read the in-depth guidePython Dictionaries Explained — Ordering, Lookups, and Merging

Yes. Since Python 3.7, dictionaries preserve insertion order as a language guarantee — iterating a dict yields keys in the order they were first added. (This was an implementation detail in CPython 3.6, then made official in 3.7.)

d = {}
d["b"] = 1
d["a"] = 2
d["c"] = 3
list(d)            # ['b', 'a', 'c'] — insertion order, not sorted

d["b"] = 9         # updating a value does NOT change order
list(d)            # ['b', 'a', 'c']

Note that updating an existing key keeps its original position; only the first insertion fixes the order. Reassigning doesn't move it. Because ordering is guaranteed, plain dict now covers most cases that once needed OrderedDict.

d[key] raises KeyError if the key is missing. d.get(key, default) returns a default (or None) instead of raising — a safe read. d.setdefault(key, default) returns the existing value if present, but if missing it inserts the default and returns it.

d = {"a": 1}
d["b"]               # KeyError
d.get("b")           # None — no error
d.get("b", 0)        # 0   — supplied default

d.setdefault("a", 99)  # 1  — already present, unchanged
d.setdefault("c", []).append(5)  # inserts c=[], then appends -> {'c': [5]}

Use get for a safe lookup that doesn't mutate, and setdefault to read-or-initialize in one step (handy for grouping). For heavy grouping work, collections.defaultdict is usually cleaner.

The modern way is the | merge operator (Python 3.9+), which returns a new dict; |= merges in place. Before 3.9, the idiom was {**a, **b} unpacking, and dict.update() merges in place.

a = {"x": 1, "y": 2}
b = {"y": 9, "z": 3}

a | b           # {'x': 1, 'y': 9, 'z': 3}  — new dict (3.9+)
{**a, **b}      # {'x': 1, 'y': 9, 'z': 3}  — same, works pre-3.9

a.update(b)     # mutates a in place -> {'x': 1, 'y': 9, 'z': 3}

In every approach the right-hand dict wins on key collisions (y becomes 9). Use | for a clean new dict on modern Python, {**a, **b} for compatibility, and update()/|= when you want to mutate in place.

They return view objects — dynamic, read-only windows onto the dict that reflect changes live rather than copying the data. They're iterable and support set-like operations, but they're not lists.

d = {"a": 1, "b": 2}
keys = d.keys()
d["c"] = 3
list(keys)          # ['a', 'b', 'c'] — view updated automatically!

keys[0]             # TypeError — a view isn't indexable
list(d.keys())      # ['a', 'b', 'c'] — materialize when you need a list

d.keys() & {"a"}    # {'a'} — keys views support set operations

Because a view is live, it's memory-cheap but you must list(...) it to index or snapshot it. Also avoid mutating the dict's size while iterating a view — that raises RuntimeError. Use views to iterate efficiently; copy to a list when you need a stable, indexable sequence.

A dict is a hash table: it computes hash(key) to decide which bucket the entry lives in, giving average O(1) insertion and lookup regardless of size. For this to work, a key's hash must never change, so keys must be hashable — effectively immutable.

d = {}
d[(1, 2)] = "ok"     # tuple is immutable -> hashable
d[[1, 2]] = "no"     # TypeError: unhashable type: 'list'

# O(1): lookup time doesn't grow with the dict's size
"x" in d             # hashes "x", checks one bucket — not a full scan

If a mutable key could change after insertion, its hash would change and the entry would land in the wrong bucket — you'd never find it again. That's why lists, dicts, and sets can't be keys, but tuples and frozensets can. The hash-table design is exactly what makes membership tests near-instant.

A dict comprehension builds a dictionary in one expression using {key: value for item in iterable}, optionally with a filtering if. It's the concise, readable alternative to a for loop that calls d[k] = v.

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

prices = {"apple": 3, "pear": 0, "fig": 7}
in_stock = {k: v for k, v in prices.items() if v > 0}
# {'apple': 3, 'fig': 7}

inverted = {v: k for k, v in prices.items()}   # swap keys/values

Like other comprehensions it has its own scope (no leaked loop variable) and is generally faster than the equivalent loop. Use it to transform, filter, or invert mappings clearly — but keep it readable rather than cramming in logic.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.