Skip to main content

Dictionaries

Level 1: Level 1: Foundationseasy10 mindictionarieskey-valuegetmerge

Map keys to values: read safely, assign, and merge dictionaries.

Why dictionaries matter

When your question is "what value goes with this key?", a dictionary answers it fast. A list forces you to scan element by element to find a match, and that cost grows with the list. A dict jumps straight to the value. Real systems lean on this everywhere: counting events, caching results, indexing rows by id, grouping records, and passing named config around. Any "user id to profile" map or word-count tally is a dict.

The mental model: a hash map

A dictionary stores key: value pairs. Under the hood it is a hash map. Python runs each key through a hash function to find the slot where its value lives, so a lookup takes about the same time whether the dict holds 10 pairs or 10 million. That average O(1) lookup, insert, and delete is the reason the type exists.

Two consequences of the hash-map design:

  • Keys must be hashable, which in practice means immutable. str, int, and tuple work as keys; a list does not and raises TypeError.
  • Since Python 3.7 a dict keeps insertion order, so iterating returns keys in the order you added them.

Reading and writing

Check yourself
prices holds one pair, apple mapped to 3. What does prices['banana'] do?

Index a key with d[key], but a missing key raises KeyError. Reach for .get(key, default) when the key might be absent and you want a fallback instead of a crash:

prices = {"apple": 3, "pear": 2}
prices["apple"]            # 3
prices.get("banana", 0)    # 0, the default, because "banana" is absent
prices["plum"] = 4         # bracket assignment adds a new pair
prices["apple"] = 5        # the same syntax updates an existing key

That .get(name, 0) pattern is exactly what the lookup exercise needs.

Check yourself
prices maps apple to 3 and pear to 2. What does the expression 3 in prices report?
Table
Only the missing-key column differs, and only one row there raises. Two of the others return something falsy without complaint, which is why a bare .get(key) so often turns a typo into a silent None instead of an error.
You writeKey is presentKey is missing
d[key]the valueraises KeyError
d.get(key)the valueNone, silently
d.get(key, 0)the value0, the fallback you chose
d.setdefault(key, 0)the valueinserts 0 into d and returns it
key in dTrueFalse, and it tests KEYS, never values
Only the missing-key column differs, and only one row there raises. Two of the others return something falsy without complaint, which is why a bare .get(key) so often turns a typo into a silent None instead of an error.
Check yourself
Two dicts both hold the key x, the first mapping it to 1 and the second to 9. You merge them in that order. What is the merged result?

Merge two dicts into a brand new one with {**a, **b}. The demo below spreads {"fig": 6} into prices and leaves both originals untouched. When both sides share a key, the right-hand dict wins:

{**{"x": 1}, **{"x": 9}}   # {'x': 9}, b overrides a on the shared key

That is the merge_two exercise in one line. Python 3.9+ also offers a | b for the same result.

Pitfalls

.get with no default returns None, not an error, when the key is missing, so prices.get("banana") gives None rather than 0. Always pass the fallback you actually want. Watch the direction too: key in d tests keys, not values, so "apple" in prices is True but 3 in prices is False. And bracket assignment overwrites silently, so d[key] = value replaces any existing value with no warning. That same rule is why the right operand wins in a merge.

Interview nuance: interviewers probe why dict lookup is O(1) while list membership (x in some_list) is O(n). The dict hashes the key and jumps to a slot; the list compares element by element. When a solution repeatedly asks "have I seen this before?", swapping a list for a dict or set is often the entire optimization, turning an O(n²) loop into O(n).

Worked example (Python)
prices = {"apple": 3, "pear": 2}
print(prices["apple"])          # 3
print(prices.get("banana", 0))  # 0
print({**prices, **{"fig": 6}})  # {'apple': 3, 'pear': 2, 'fig': 6}

Apply

Your turn

The task this lesson builds to.

Implement lookup(prices, name): return the price for name from the prices dict, or 0 if it isn't there.

For prices = {"apple": 3} and name = "banana", return 0.

2 hints and 4 automated checks are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Implement merge_two(a, b): return a new dict with all pairs from a and b. When a key is in both, b's value wins.

For ({"x": 1}, {"x": 9}) return {"x": 9}.

2 hints and 4 automated checks are waiting in the workspace.