Question bank — Dictionaries — key-value pairs, access, methods (keys, values, items, get, update)
Picture the machinery first
The traps below all trace back to one picture: a dictionary is an array of buckets, and the key's hash() chooses the bucket. Study these three figures once, and most answers become obvious.
1 — How a key finds its bucket. The key is run through hash(), that number is squeezed into a bucket index, and the value is filed there. Reading is the same jump — one hop, not a scan. This is the whole reason lookup is fast.

2 — Collisions and the worst case. Two different keys can land in the same bucket (a collision). Python then stores both in that bucket and, on lookup, uses the key's == (__eq__) test to tell them apart. If every key collided into one bucket, lookup would degrade to a linear scan — average , worst case . This is why a key needs both a good __hash__ (spreads keys out) and a correct __eq__ (resolves the ties).

3 — Why a view is "dynamic". keys()/values()/items() don't copy anything — they're a window aimed at the live bucket array. Insert, delete, or clear() the dict, and the same view shows the change immediately.

True or false — justify
A dictionary can contain two identical keys.
{"a":1,"a":2} doesn't error; the second value silently overwrites the first, leaving {"a":2}.d.get("x") and d["x"] behave the same when the key exists.
get returns None, brackets raise KeyError.Dictionaries have been unordered forever in Python.
A tuple can always be used as a dictionary key.
(1,2) works, but (1,[2]) raises TypeError because the inner list is unhashable.d.keys() returns a list of the keys.
list() to become an actual list.Two different keys can map to the same value.
{"a":1,"b":1} is perfectly legal.Adding a key that already exists creates a second entry.
len(d) counts the total number of keys plus values.
len 3, not 6.Iterating a dict with for x in d gives you the values.
d.values() for values or d.items() for both.d.values() guarantees the values have no duplicates.
Dict lookup is always , no matter what.
Spot the error
d = {[1,2]: "pair"}
list is mutable and therefore unhashable, so it cannot be a key. This raises TypeError: unhashable type: 'list'. Use a tuple (1,2) instead.for k, v in d: print(k, v)
ValueError (or unpacks the key's characters). You need d.items() to get (key, value) pairs.total = sum(d) where d = {"a":90,"b":85}
sum(d) sums the keys (strings here → TypeError). To add the numbers use sum(d.values()).city = d["city"] # I expect None if absent
None on a miss — they raise KeyError. For a None-on-miss result you must use d.get("city").d.update("age", 20)
update doesn't take two positional arguments. Valid forms are a mapping d.update({"age":20}), an iterable of pairs d.update([("age",20)]), or keyword arguments d.update(age=20) — or simply d["age"] = 20.del d["x"] when "x" may not exist
del on a missing key raises KeyError. Guard with if "x" in d: first, or use d.pop("x", None) which won't crash.keys = d.keys(); keys.append("z")
.append and cannot be edited — it mirrors the dict, it doesn't own the data. Add via the dict itself: d["z"] = ....if d.get("x") == None: ... and treating a real stored None as "missing"
None, .get() returns None for both "absent" and "present-but-None" — indistinguishable. Use "x" in d when you must tell them apart.Why questions
Why must dictionary keys be hashable?
hash(key) (figure 1). If a key's hash could change (like a mutable list), Python could no longer find the bucket it filed the value into, so only stable-hash (immutable) keys are allowed.Why is key lookup in a dict average , while searching a list for a value is ?
lst[3]) is also — it's searching a list for a matching value (x in lst / lst.index(x)) that costs because it scans element by element.Why does a good key need both __hash__ and __eq__?
__hash__ picks the bucket (spreading keys out so most buckets hold one item); __eq__ resolves collisions inside a bucket by confirming which stored key truly matches. Without a correct __eq__, colliding keys couldn't be told apart.Why does .get() exist when brackets already read values?
.get() lets a program continue when a key might be absent, returning None or a chosen default instead of crashing — the common real-world case of "look, but it might not be there."Why are keys()/values()/items() called views rather than copies?
Why can a tuple be a key but a list cannot?
Why does d["age"] = 20 then d["age"] = 21 leave only one entry?
Edge cases
What does {} mean — an empty dict or empty set?
{} is an empty dict. An empty set must be written set(), because the curly-brace default belongs to dictionaries.What happens if you use True and 1 as separate keys?
True == 1 and hash(True) == hash(1) — so {True: "a", 1: "b"} becomes {True: "b"} with one entry; the later value wins and the key keeps its first form.Is d.pop("x") safe if "x" is missing?
KeyError. Supply a fallback like d.pop("x", None) to make it safe.What does iterating an empty dict do?
Can a dict be a value inside another dict?
Can a dict be a key inside another dict?
frozenset or tuple if you need a hashable stand-in.If you modify a dict while looping over d.items(), what happens?
RuntimeError: dictionary changed size during iteration. Loop over list(d.items()) if you must mutate.What is the worst-case lookup cost if every key hashes to one bucket?
==, exactly like a list scan.Recall One-line survival summary
Keys: unique + hashable, needing both __hash__ (bucket) and __eq__ (tie-break). Brackets bang, .get() is gentle. Views are live windows, not copies. Lookup is average / worst. Assignment overwrites, never duplicates. Iteration hands you keys. {} is a dict, set() is the empty set.
Connections
- Hinglish version
- Hashing and Hash Tables
- Tuples — immutable sequences
- Sets — unique hashable elements
- Lists — ordered mutable sequences
- For loops and iteration
- Big-O Notation — time complexity