Topic 4.4
Dictionaries: Look Things Up by Key
In one line
A dictionary stores pairs: each key leads to a value, and you look a value up by its key instead of by position. Dictionaries keep the order in which keys were added (guaranteed since Python 3.7) and are the shape of almost all JSON data.
Think of it like this
A phone book or a school register. You don't count to the 57th entry; you look up a name and get a number. Python's dictionary (dict) works the same way: you give it a key, and it gives back the value stored for that key.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Dictionary (dict)
- A collection of key-value pairs where you look up a value by its key.
- Key
- The label used to look something up. Keys are unique and must be hashable, meaning they never change (strings, numbers and tuples of them are fine; Topic 4.5 explains why).
- Value
- The data stored under a key. It can be any object.
- Pair (item)
- One key together with its value.
- KeyError
- The error raised when you read a key that is not in the dictionary.
- View
- The live result of
keys(),values()oritems(). It reflects later changes to the dictionary. - Insertion order
- The order in which keys were first added. Python 3.7 and later keep it.
- Merge
- Combining two dictionaries into one.
a | bis the Python 3.9 way.
Step by step
01Create a dictionary and look up a value
Square brackets with a key look up the value. This is not an index; there is no position number. The key can be a string, a number, or a tuple.
prices = {"apple": 30, "banana": 12, "mango": 50}
print(prices["apple"]) # 30
print(len(prices)) # 3
print(prices)02Add, change and remove entries
The same assignment adds a new key or changes an existing one. pop removes a key and hands you its value; give pop a second argument to avoid a KeyError when the key might be missing.
prices = {"apple": 30, "banana": 12}
prices["kiwi"] = 40 # new key: added
prices["apple"] = 35 # existing key: replaced
removed = prices.pop("banana")
del prices["kiwi"]
print(prices, removed)03Safe reading with get, in and setdefault
get never raises: a missing key gives None or your default. in tests keys. setdefault is a read that creates the entry if it was missing, which is useful for building groups. Topic 4.10 shows defaultdict, a neater tool for the same job.
prices = {"apple": 30}
print(prices.get("kiwi")) # None
print(prices.get("kiwi", 0)) # 0
print("apple" in prices) # True
print(30 in prices) # False: 30 is a value, not a key
prices.setdefault("grape", 60) # stores 60 and returns it
prices.setdefault("grape", 99) # exists: keeps 6004Loop over keys, values and items
A plain for over a dictionary gives keys. .items() gives (key, value) pairs, and tuple unpacking (Topic 4.3) splits each pair into two loop variables. The loop follows insertion order.
prices = {"apple": 30, "banana": 12}
for fruit in prices:
print(fruit)
for price in prices.values():
print(price)
for fruit, price in prices.items():
print(fruit, "costs", price)05Keys must be unique and hashable
Writing a key twice keeps the later value. A list cannot be a key, because it can change and a key must stay findable; Topic 4.5 explains why in full. Numbers, strings, booleans, None and tuples of those are fine.
d = {"a": 1, "a": 2}
print(d) # {'a': 2}
# d[[1, 2]] = "x" # TypeError: unhashable type: 'list'06Order and merging
Because of insertion order, print shows keys in the order you added them. A merge with | makes a new dictionary: first the left-hand pairs, then the right-hand ones, with the right-hand value replacing a left-hand value for a shared key. The merge operator needs Python 3.9 or newer.
defaults = {"theme": "light", "size": 12}
mine = {"size": 16}
settings = defaults | mine
print(settings) # {'theme': 'light', 'size': 16}07Dictionaries in the AI world
Every message you send to a chat model is a dictionary with a "role" (who is speaking) and a "content" (the text). The conversation is a list of those dictionaries. The reply you get back is also nested dictionaries and lists. Reading and building these is the everyday job of an AI engineer, and Topic 4.8 goes deeper.
conversation = [
{"role": "system", "content": "You are a patient teacher."},
{"role": "user", "content": "What is a dictionary?"},
]
conversation.append({"role": "assistant", "content": "A lookup table."})Try it yourself
- 1
Predict the order
In the first example, work out the order of the keys in the line that prints
final:before you run it. Which keys keep their place when you update a value, and where do new keys go? - 2
Count with get
In the second example, change the word
"banana"in the counting loop to your own name and run it. The linecounts.get(letter, 0) + 1is the standard counting trick: "the old count, or 0 if I haven't seen this letter".
Code & diagrams
Change a key name in one of the lookups to see KeyError, then use get to avoid it.
Expected output
apple: 30
kiwi (get): None
kiwi (get with default): 0
'apple' in prices: True
30 in prices: False
after changes: {'apple': 35, 'kiwi': 40} | removed: 50
final: {'apple': 35, 'kiwi': 45, 'grape': 60, 'plum': 20} | entries: 4
apple costs 35
kiwi costs 45
grape costs 60
plum costs 20
keys: ['apple', 'kiwi', 'grape', 'plum']
values: [35, 45, 60, 20]`dict(zip(names, numbers))` pairs up two lists item by item (`zip` is Topic 2.6) and builds a dictionary from the pairs.
Expected output
system -> You are a patient teacher.
user -> What is a dictionary?
assistant -> A dictionary maps keys to values.
last message: A dictionary maps keys to values.
from zip: {'a': 1, 'b': 2, 'c': 3}
letter counts: {'b': 1, 'a': 3, 'n': 2}The right-hand value wins when both dictionaries have the same key.
Expected output
merged: {'theme': 'light', 'size': 16, 'language': 'hi'}
defaults unchanged: {'theme': 'light', 'size': 12}
defaults after |=: {'theme': 'light', 'size': 16, 'language': 'hi'}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Read a key that does not exist
Look up a key that was never added.
prices = {"apple": 30, "banana": 12}
print(prices["kiwi"])Break #2
Add keys while looping over the dictionary
Add a new entry inside a loop over the same dictionary.
prices = {"apple": 30, "banana": 12}
for fruit in prices:
prices[fruit + "_sale"] = 0Myth vs fact
Myth
Dictionaries have no order.
Fact
Since Python 3.7 they keep insertion order as part of the language. Two dictionaries with the same pairs in different orders are still equal (==), but they print and loop differently.
Myth
x in d checks the values.
Fact
It checks the keys. To test values use x in d.values(), which is a slow scan.
Myth
d.get(key) and d[key] are the same.
Fact
d[key] raises KeyError for a missing key; d.get(key) returns None or your default. Pick on purpose: [] for must-exist keys, get for optional ones.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
CPython's dict is a compact hash table with a separate array that stores entries in insertion order (Topic 4.5 explains the hashing). That design is why order is free, why lookup and insertion average O(1), and why deleting leaves holes that are cleaned up on the next resize.
- ▸
dict.getevaluates its default argument every time, even when the key exists.d.get(k, expensive())callsexpensive()on every call. Useif k in dorsetdefault/defaultdictwhen the default is costly. - ▸
Dictionary views support set operations:
a.keys() & b.keys()gives the common keys anda.keys() - b.keys()the keys only ina. This is a tidy way to compare two configs. - ▸
Python 3.9 added
|and|=for dicts, and{**a, **b}is the older way that works everywhere. In APIs for AI models you often build a request with{**defaults, **overrides}so callers can change only what they need. All of these merges are shallow: if both sides hold a dictionary under the same key, the right-hand dictionary replaces the left-hand one completely, it is not combined ({"a": {"x": 1, "y": 2}} | {"a": {"x": 9}}is{"a": {"x": 9}}). Also,d |= pairsaccepts any iterable of key-value pairs, likeupdate, whiled | otherneeds another dictionary. - ▸
Smaller facts that interviewers like:
popitem()removes and returns the last inserted pair (LIFO, since 3.7),reversed(d)works since Python 3.8, anddict.fromkeys(keys, value)shares onevalueobject among all keys, sodict.fromkeys(names, [])gives every key the same list (use a comprehension instead). Equality ignores order, butlist(a) == list(b)does not.
Remember this
- 1
You write a dictionary with curly brackets and
key: valuepairs separated by commas:prices = {"apple": 30, "banana": 12}.prices["apple"]gives 30. An empty dictionary is{}. Keys must be unique: if you write the same key twice, the later value wins. Values can be anything, including lists and other dictionaries. - 2
To add or change an entry, assign to a key:
prices["kiwi"] = 40. If the key is new, it is added; if it exists, its value is replaced.del prices["banana"]removes an entry, andprices.pop("banana")removes it and returns its value.len(prices)counts the entries. - 3
Reading a key that is not there raises
KeyError. Two safer tools:prices.get("kiwi")returnsNonewhen the key is missing (or a default you give:prices.get("kiwi", 0)), and"kiwi" in priceschecks the keys (not the values) and givesTrueorFalse.setdefault(key, default)returns the value if the key exists, and otherwise stores the default and returns it.update(other)copies all the pairs of another dictionary in. - 4
To go through a dictionary:
for key in pricesvisits the keys,prices.values()the values andprices.items()the pairs, which you normally unpack:for fruit, price in prices.items():. These three are views, live windows onto the dictionary, not copies. Wrap them inlist(...)when you need a snapshot. - 5
Since Python 3.7, a dictionary remembers the order in which keys were first added, and loops and printing follow that order. Updating the value of an existing key keeps its place; deleting a key and adding it again puts it at the end. Older code and other languages may treat dictionaries as unordered, but you can rely on this in modern Python.
- 6
Two dictionaries can be merged into a new one with
a | b(Python 3.9); when both have the same key, the right-hand value wins.a |= bupdatesain place. Dictionaries are everywhere in AI work: one chat message is{"role": "user", "content": "..."}, and a whole conversation is a list of such dictionaries. That list is what a program sends to a model through its API (the doorway a program uses to ask another program for something). Dictionaries are also the shape of JSON, the text format that programs use to send data to each other; Topic 4.8 shows it.
Explain it without notes
How is a dictionary different from a list, and when do you use each?
What is the difference between d[key] and d.get(key)?
Does a dictionary keep order? Since which version?
How do you loop over the keys and values of a dictionary together?
What are the ways to merge two dictionaries, and what does "the right-hand value wins" mean for nested dictionaries?
Practice
Make a dictionary student = {"name": "Asha", "age": 21}. Add "city": "Delhi", change age to 22, then print every key and value as key: value.
Given marks = {"maths": 80, "science": 90}, print the mark for "english" as 0 if it is missing, without raising an error.
Count how many times each word appears in "to be or not to be" and print the dictionary.
Trade-offs
- ↔
Fast lookup costs memory. A dictionary keeps a hash table with spare room, so it uses more memory than a list of the same data. In return, finding a key does not slow down as the dictionary grows.
- ↔
Keys are strings, so typos are silent until you read them.
user["naem"]fails only when that line runs. For data with a fixed shape, a dataclass or a Pydantic model (Topic 15.4) catches such mistakes earlier.
Done when you can
Done when you can create a dictionary, read, add, change and delete entries.
Done when you can choose between
d[key],d.get(key)andkey in d.Done when you can loop with
.items()and explain what.keys()and.values()return.Done when you can say that dictionaries keep insertion order (since 3.7) and merge them with
|(3.9).Done when you can build a list of message dictionaries like the ones AI model APIs use.