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 MergingYes. 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.