Exercises — Dictionaries — key-value pairs, access, methods (keys, values, items, get, update)
Throughout, d means a Python dictionary — a collection of key-value pairs written {key: value, ...}. A key is the label you look things up by; a value is what you get back. If any of these words feels shaky, re-read the parent note first.
Level 1 — Recognition
Goal: can you read dictionary syntax and predict simple outputs?
L1.1 — Spot the valid dictionary
Which of these are valid Python dictionaries? For invalid ones, say why.
a = {"x": 1, "y": 2}
b = {1: "one", 2: "two"}
c = {[1, 2]: "list"}
d = {(1, 2): "tuple"}
e = {"x": 1, "x": 2}Recall Solution
a✅ valid — string keys, integer values.b✅ valid — integers are hashable, so they make fine keys.c❌ invalid — a list is mutable/unhashable →TypeError: unhashable type: 'list'. A key's hash must never change; a list can change, so Python forbids it.d✅ valid — a tuple is immutable, therefore hashable, therefore a legal key.e✅ syntactically valid but the duplicate key"x"collapses: the later value wins, soe == {"x": 2}. No error, just silent overwrite.
L1.2 — Read the output
p = {"name": "Ravi", "age": 19}
print(p["name"])
print(p.get("city"))
print(p.get("city", "unknown"))What does each line print?
Recall Solution
p["name"]→Ravi(bracket access finds the key directly).p.get("city")→None— the key"city"is absent, and.get()returnsNoneinstead of crashing.p.get("city", "unknown")→unknown— the second argument is the default returned when the key is missing.
L1.3 — What does iteration give?
scores = {"math": 90, "sci": 85}
for x in scores:
print(x)What gets printed — keys or values?
Recall Solution
Prints the keys: math then sci. Iterating a dict directly yields its keys, never its values. To loop over values use scores.values(); to loop over both use scores.items().
Level 2 — Application
Goal: can you use the methods to build and change dictionaries?
L2.1 — Build and mutate
Start from d = {"name": "Asha"}. Apply these lines in order and give the final dictionary:
d["age"] = 20
d["age"] = 21
d.update({"city": "Pune", "age": 22})
del d["name"]Recall Solution
Track it step by step:
d["age"] = 20→{"name": "Asha", "age": 20}(new key created).d["age"] = 21→{"name": "Asha", "age": 21}(existing key overwritten, not duplicated).d.update({"city": "Pune", "age": 22})→ adds"city", overwrites"age"→{"name": "Asha", "age": 22, "city": "Pune"}.del d["name"]→ removes that pair →{"age": 22, "city": "Pune"}.
L2.2 — Sum the values
prices = {"pen": 10, "book": 45, "bag": 300}
total = sum(prices.values())What is total?
Recall Solution
prices.values() is a view of [10, 45, 300]. sum(...) adds them: . So total == 355.
Why .values() and not the dict itself? sum(prices) would try to add the keys ("pen" + "book" + ...) and crash on strings. You must ask for the values explicitly.
L2.3 — Safe counting with .get()
Count how many times each letter appears in "banana" using a dictionary.
counts = {}
for ch in "banana":
counts[ch] = counts.get(ch, 0) + 1What is counts?
Recall Solution
.get(ch, 0) returns the current count, or 0 if ch isn't in the dict yet — so the very first time we see a letter we add 0 + 1.
b: 1a: appears at positions 2, 4, 6 → 3n: appears at positions 3, 5 → 2
Result: {"b": 1, "a": 3, "n": 2} (insertion order: b, a, n).
Level 3 — Analysis
Goal: can you explain why dictionary code behaves as it does?
L3.1 — Views are live
d = {"a": 1, "b": 2}
v = d.keys()
d["c"] = 3
print(list(v))What prints, and why?
Recall Solution
Prints ['a', 'b', 'c']. d.keys() returns a dynamic view, not a snapshot copy. When we add "c" after creating v, the view still points at the live dict, so it now shows the new key too. Views are windows, not photographs.
L3.2 — Overwrite vs. new
Explain the difference between these two lines even though they look identical:
d["age"] = 20 # first time
d["age"] = 21 # second timeRecall Solution
Syntax is identical: d[key] = value. The behaviour differs because of what already exists:
- First line: key
"age"is absent → Python creates a new pair. - Second line: key
"age"already exists → Python overwrites the value.
This is forced by the rule that keys are unique: assigning to an existing key can only replace, never duplicate. The dict has no way to hold two "age" entries.
L3.3 — Predict the crash
Which of these lines raises an error? Name the error.
d = {"x": 1}
print(d["x"]) # A
print(d["y"]) # B
print(d.get("y")) # C
d[[1,2]] = 5 # DRecall Solution
- A — no error, prints
1. - B — raises
KeyError('y'not present). Bracket access on a missing key crashes. - C — no error, prints
None..get()is the gentle version. - D — raises
TypeError: unhashable type: 'list'. A list cannot be a key.
Level 4 — Synthesis
Goal: can you combine the tools into a small working program?
L4.1 — Invert a dictionary
Given d = {"a": 1, "b": 2, "c": 3}, write code that swaps keys and values, producing {1: "a", 2: "b", 3: "c"}.
Recall Solution
d = {"a": 1, "b": 2, "c": 3}
inv = {}
for k, v in d.items():
inv[v] = k
# inv == {1: "a", 2: "b", 3: "c"}We use .items() to grab both pieces at once, then store value → key in a fresh dict. Caution: this only works cleanly if the values are unique and hashable. If two keys shared a value, one would overwrite the other. One-liner version: inv = {v: k for k, v in d.items()}.
L4.2 — Merge two gradebooks
Two dictionaries record marks. Merge them so b overrides a on shared subjects.
a = {"math": 70, "sci": 80}
b = {"sci": 95, "eng": 88}Produce the merged result and give the final dict.
Recall Solution
merged = dict(a) # copy so 'a' is untouched
merged.update(b) # b's values win on collisions"sci" appears in both; update lets b's value (95) overwrite a's (80). Final:
{"math": 70, "sci": 95, "eng": 88}.
(Python 3.9+ one-liner: merged = a | b.)
L4.3 — Frequency then find the max
Using the counts idea, find the most frequent letter in "mississippi".
counts = {}
for ch in "mississippi":
counts[ch] = counts.get(ch, 0) + 1
top = max(counts, key=counts.get)What are counts and top?
Recall Solution
Counting: m→1, i→4, s→4, p→2. So counts == {"m": 1, "i": 4, "s": 4, "p": 2}.
max(counts, key=counts.get) scans the keys and compares each by its count. Both i and s tie at 4; max returns the first one it encountered at that maximum, which is "i" (it appears before s in insertion order). So top == "i".
Level 5 — Mastery
Goal: can you reason about the machinery — hashing and complexity?
L5.1 — Why O(1)?
A friend claims: "Looking up d["Asha"] gets slower as the dict grows, just like searching a list." Refute this in terms of the hash table.
Recall Solution
False. A list lookup scans element by element → time grows with size → . A dict computes hash("Asha") → an address in the hash table → reads the value there directly. That computation costs the same whether the dict holds 5 or 5 million entries → average , independent of size. See Big-O Notation — time complexity. (See the figure below.)

L5.2 — Reinvent the lookup, measure the cost
Here is the "no dictionaries exist" version using parallel lists:
keys = ["a", "b", "c", "d"]
values = [ 1, 2, 3, 4 ]
def lookup(k):
for i in range(len(keys)):
if keys[i] == k:
return values[i]For lookup("d") on this 4-element list, how many key comparisons happen? What's the worst case for a list of length ? How does a dict differ?
Recall Solution
"d" is the last key, at index 3, so the loop compares keys[0], keys[1], keys[2], keys[3] → 4 comparisons. In general the worst case (key at the end, or absent) needs comparisons → . A dict replaces this whole scan with one hash computation and a direct read → on average. That is why the syntax d[k] exists: the parallel-list pattern, made automatic and fast.
L5.3 — Collisions and the "average" caveat
The claim is " on average." When could a dict lookup slow down, and why does Python still call it average?
Recall Solution
Two different keys can hash to the same address — a collision. Python stores such keys together at that slot and must check them one by one. If many keys collided, a lookup could degrade toward in the theoretical worst case. In practice Python uses good hash functions and resizes the table so collisions stay rare, keeping the average cost . The figure contrasts the clean single-slot hit with a small collision chain.

Recall One-line self-check (cover the answers)
Dict lookup average complexity? ::: via hash table
d.get("z") when "z" absent? ::: returns None (no crash)
{"a":1,"a":2} becomes? ::: {"a": 2} (later value wins)
for x in d yields? ::: keys
Merge b into a? ::: a.update(b) (b overwrites shared keys)
Are views copies? ::: no — live/dynamic views
Connections
- Lists — ordered mutable sequences (the scan we replaced)
- Tuples — immutable sequences (valid, hashable keys)
- Sets — unique hashable elements (same hash-table machinery)
- Hashing and Hash Tables
- For loops and iteration
- Big-O Notation — time complexity