This page is a firing range : every kind of situation a dictionary can throw at you, worked out one by one. If you have not yet met the basics, read the parent Dictionaries topic note first — here we assume you know that a dictionary is a set of {key: value} pairs and we drill every edge case.
Before anything else, one plain-words reminder so no symbol sneaks in unearned:
Definition The three words we lean on
Key — the label you look things up by (like the word "cat" in a real dictionary). Must be unique and hashable (unchangeable: str, int, tuple).
Value — the thing stored under that label (the definition of "cat"). Can be anything .
Missing key — you asked for a label that was never put in. This is the single most common source of bugs, so half our examples are about it.
Think of every dictionary operation as landing in one cell of this grid. Our goal: hit every cell with at least one worked example.
#
Case class
What makes it tricky
Example that hits it
C1
Key present — plain read
The happy path
Ex 1
C2
Key absent — bracket vs .get()
Crash vs graceful
Ex 2
C3
Create vs overwrite
Same syntax, opposite effect
Ex 3
C4
Empty dictionary {}
Degenerate / zero input
Ex 4
C5
Merging with update — shared keys
Who wins the collision?
Ex 5
C6
Illegal key — list/mutable
TypeError, and the fix
Ex 6
C7
Counting / accumulating (.get default trick)
The 80% real-world loop
Ex 7
C8
Iterating keys vs values vs items
The classic wrong-loop bug
Ex 8
C9
Real-world word problem
Model reality as a dict
Ex 9
C10
Exam twist — nested + view liveness
Views are live , not copies
Ex 10
Below, each example names the cell it covers. Figures appear where geometry/structure helps you see what the machine does.
The figure above is the mental model for every example: a key goes through hash(), that number points at a slot, the slot holds the value. Keep this picture in mind — "absent key" just means the slot is empty .
Worked example Read a key that exists
book = { "title" : "Dune" , "pages" : 412 }
print (book[ "pages" ])
Forecast: What prints? Guess before reading on.
Steps
Python computes hash("pages") → a slot address.
Why this step? Bracket access d[k] never scans; it jumps straight to the slot (average O ( 1 ) , see Big-O Notation — time complexity ).
The slot is occupied by 412, so that value is returned.
Why this step? The key is present, so no error can occur.
Verify: book["pages"] returns 412. Sanity check: 412 is the value we literally typed after "pages":, so the read must reproduce it.
Worked example The same missing key, two behaviours
book = { "title" : "Dune" , "pages" : 412 }
a = book.get( "author" ) # ?
b = book.get( "author" , "Unknown" ) # ?
# c = book["author"] # what would this do?
Forecast: Three lines, three different fates. Predict each.
Steps
book.get("author") → the slot for "author" is empty, so .get returns the sentinel value ==None==.
Why this step? .get is designed to never crash ; missing → None.
book.get("author", "Unknown") → empty slot, but we supplied a fallback, so it returns "Unknown".
Why this step? The second argument is the "if not found, use this" default.
book["author"] → empty slot with no fallback available → raises KeyError.
Why this step? Bracket access has no way to say "nothing here", so it must fail loudly.
Verify: a is None, b is "Unknown", and book["author"] raises KeyError. Sanity check: only the bracket form crashes — matching the mnemonic bracket = Bang, get = Gentle .
.get() also crashes on missing keys"
Why it feels right: both read a value. Truth: .get() returns None/default; only the bracket form raises KeyError.
Worked example Watch the same line do two different jobs
d = {}
d[ "age" ] = 20 # (i)
d[ "age" ] = 21 # (ii)
Forecast: After both lines, how many pairs are in d? What is d["age"]?
Steps
Line (i): key "age" has no slot yet → Python creates the pair. Now d = {"age": 20}.
Why this step? Assignment to a new key adds it.
Line (ii): key "age" already occupies a slot → Python overwrites the value in place. Now d = {"age": 21}.
Why this step? Keys are unique ; assigning to an existing key replaces, never duplicates.
Verify: len(d) == 1 and d["age"] == 21. Sanity check: the count did not grow to 2 — proof that (ii) overwrote rather than added.
Worked example What do the methods do when there is nothing?
empty = {}
print ( len (empty)) # ?
print ( list (empty.keys())) # ?
print ( sum (empty.values())) # ?
print (empty.get( "x" , 0 )) # ?
Forecast: Does any line crash? Guess the four outputs.
Steps
len(empty) → 0. Why: zero pairs, so length is zero. No crash — empty is a valid dict.
list(empty.keys()) → []. Why: the view has nothing to show, so listing it gives an empty list.
sum(empty.values()) → 0. Why: summing an empty collection is the identity of addition, which is 0.
empty.get("x", 0) → 0. Why: every key is absent in an empty dict, so the default is returned.
Verify: outputs are 0, [], 0, 0. Sanity check: the empty dict is the "zero" of dictionaries — every operation returns the neutral result instead of erroring. This is why looping for k in {} simply does nothing (see For loops and iteration ).
Worked example Two dicts merge; a shared key clashes
a = { "x" : 1 , "y" : 2 }
b = { "y" : 99 , "z" : 3 }
a.update(b)
Forecast: After the merge, what is a["y"]? Does a still have "x"?
Steps
update walks through every pair of b.
Why this step? It merges b into a , one pair at a time, in place.
"y" exists in both → the value from b overwrites → a["y"] becomes 99.
Why this step? On a key collision, the incoming (argument) value wins.
"z" is new → it is added. "x" was untouched → it stays.
Why this step? Non-clashing keys survive unchanged.
Verify: a == {"x": 1, "y": 99, "z": 3}. Sanity check: three distinct keys (x,y,z), and the clash resolved in favour of b.
Worked example Try to use a list as a key
seen = {}
# seen[[1, 2]] = "pair" # crashes!
seen[( 1 , 2 )] = "pair" # works
Forecast: Which line explodes, and with what error name?
Steps
seen[[1, 2]] = ... → Python must compute hash([1,2]), but lists are mutable / unhashable → TypeError: unhashable type: 'list'.
Why this step? A key's hash must stay fixed forever; a list could change, so it is banned. (See Hashing and Hash Tables .)
seen[(1, 2)] = "pair" → a tuple is immutable, hence hashable → the pair is stored fine.
Why this step? Swap the mutable list for its immutable twin, the tuple, and the machinery accepts it.
Verify: seen[(1, 2)] == "pair" and len(seen) == 1; the list version raises TypeError. Sanity check: only the immutable key survives — exactly the hashability rule.
Worked example Count letter frequencies
word = "banana"
freq = {}
for ch in word:
freq[ch] = freq.get(ch, 0 ) + 1
Forecast: What is freq at the end? How many a's?
Steps
Start freq = {} (empty).
Why this step? We accumulate counts as we go.
For each character, freq.get(ch, 0) reads the current count — or 0 if the letter is new.
Why this step? .get(ch, 0) avoids a KeyError on the first time we see a letter; without the default this loop would crash on the first b.
Add 1 and store back. First sighting creates the pair (value 1); later sightings overwrite with a bigger number (the create-vs-overwrite rule from Ex 3).
Verify: freq == {"b": 1, "a": 3, "n": 2} and freq["a"] == 3. Sanity check: the counts must sum to the word length: 1 + 3 + 2 = 6 = len ( " banana " ) . ✓
Worked example The classic "wrong loop" bug
scores = { "math" : 90 , "sci" : 85 , "eng" : 78 }
a = [x for x in scores] # (i)
b = [x for x in scores.values()] # (ii)
c = [ f " { k } = { v } " for k, v in scores.items()] # (iii)
Forecast: Which list holds the numbers 90, 85, 78? Many beginners guess (i).
Steps
for x in scores yields the keys → a = ["math", "sci", "eng"].
Why this step? Default iteration gives keys, not values — the #1 loop mistake.
scores.values() yields the values → b = [90, 85, 78].
Why this step? You must explicitly ask for .values() to get the numbers.
scores.items() yields (key, value) tuples , which we unpack into k, v → c = ["math=90", "sci=85", "eng=78"].
Why this step? .items() hands both pieces at once, so you never re-look-up scores[k] inside the loop.
Verify: a == ["math","sci","eng"], b == [90,85,78], sum(b) == 253, and c[0] == "math=90". Sanity check: only (ii) contains numbers — confirming default iteration is over keys.
Worked example A canteen menu
Rahul runs a snack stall with prices {"samosa": 15, "chai": 10, "vada": 20}. A customer orders ["chai", "chai", "samosa"]. Compute the total bill, and handle an item that isn't on the menu.
menu = { "samosa" : 15 , "chai" : 10 , "vada" : 20 }
order = [ "chai" , "chai" , "samosa" , "coffee" ]
total = 0
for item in order:
total += menu.get(item, 0 ) # unknown item costs 0 (or flag it)
Forecast: What is the total? Notice "coffee" is not on the menu.
Steps
Model the price list as a dict — key = item name, value = price.
Why this step? We need instant price lookup by name; that is exactly what a dict gives (O ( 1 ) ), far better than searching a list of pairs.
Loop the order, adding menu.get(item, 0).
Why this step? "coffee" is absent; .get(item, 0) charges 0 instead of crashing — graceful handling of a real missing key.
Sum: chai + chai + samosa + coffee = 10 + 10 + 15 + 0 .
Verify: total == 35. Sanity check by hand: two chais (20 ) plus one samosa (15 ) plus unknown coffee (0 ) = 35 . ✓ Units are rupees throughout.
Worked example Views reflect later changes
gradebook = { "Asha" : { "math" : 90 }, "Ravi" : { "math" : 70 }}
names = gradebook.keys() # a VIEW, not a copy
gradebook[ "Meera" ] = { "math" : 88 } # add AFTER taking the view
print ( list (names))
print (gradebook[ "Asha" ][ "math" ]) # nested access
Forecast: Does names include "Meera", even though we made it before adding her? What does the nested read give?
Steps
gradebook.keys() returns a dynamic view , not a snapshot.
Why this step? Views are windows onto the live dict — cheap, no copying.
We add "Meera" after grabbing names. Because names is live, it now includes "Meera" .
Why this step? This is the exam trap: people expect the view to be frozen at creation time. It is not.
gradebook["Asha"]["math"] peels two layers: outer key "Asha" → inner dict {"math": 90} → inner key "math" → 90.
Why this step? Nested dicts are just dicts whose values are dicts; chain the brackets.
Verify: list(names) == ["Asha", "Ravi", "Meera"] and gradebook["Asha"]["math"] == 90. Sanity check: the view grew to length 3 without us re-fetching it — proof of liveness.
Recall Which cell does each example cover?
Ex 1 present-read ::: C1
Ex 2 absent key bracket vs get ::: C2
Ex 3 create vs overwrite ::: C3
Ex 4 empty dict methods ::: C4
Ex 5 update collision winner ::: C5
Ex 6 list key TypeError + tuple fix ::: C6
Ex 7 counting with get default ::: C7
Ex 8 keys vs values vs items ::: C8
Ex 9 canteen word problem ::: C9
Ex 10 nested + live views ::: C10
Mnemonic The one-line survival guide
Bracket bangs, get is gentle; assign creates or overwrites; keys must be frozen; views are alive.
Parent topic (Hinglish)
Lists — ordered mutable sequences
Tuples — immutable sequences
Sets — unique hashable elements
Hashing and Hash Tables
For loops and iteration
Big-O Notation — time complexity