Python · Intermediate
Dictionaries in Python
In short: A dictionary maps keys to values so you can look up a value by its key in constant time. Keys must be hashable and unique within the dictionary; values can be anything. Since Python 3.7 a dictionary keeps its entries in the order they were inserted.
What a dictionary is and why it is everywhere
A dictionary is a lookup table. Where a list answers "what is at position 3?", a dictionary answers "what is the stock level for bolts?". Each entry pairs a key with a value; the key is what you search by, the value is what you get back. Keys must be hashable and unique within one dictionary, so strings, numbers and tuples are common keys while lists are not allowed. Values can be any object, including lists, other dictionaries or functions.
Lookup is fast because Python hashes the key and jumps straight to its slot, so stock["bolt"] costs about the same in a dictionary of ten entries as in one of a million. That is why dictionaries appear everywhere: configuration, JSON data, caches, counting, grouping and indexing all reduce to "find the value for this key".
Reading a key that is missing raises KeyError. When absence is a normal case rather than a bug, d.get(key) returns None instead, and d.get(key, default) returns the default you choose. The pattern counts[word] = counts.get(word, 0) + 1 counts things in one line for exactly that reason.
Since Python 3.7 the language guarantees that a dictionary iterates in insertion order. for key in d visits keys, d.values() visits values and d.items() yields (key, value) pairs that you unpack in the loop header. To iterate in a different order, sort the keys or the items with sorted() and a key function.
Assignment d[key] = value either adds an entry or replaces the value of an existing key; del d[key] removes one, d.pop(key) removes and returns it, and d.update(other) merges another mapping in. Python 3.9 added the | operator, which merges two dictionaries into a new one.
Syntax
stock = {"hinge": 40, "bolt": 250} # literal
empty = {} # or dict()
stock["latch"] = 12 # add or replace
stock["bolt"] # read; KeyError if missing
stock.get("washer", 0) # read with a default
"hinge" in stock # key test
del stock["hinge"]
qty = stock.pop("bolt") # remove and return
for key, value in stock.items(): # insertion order
...
merged = stock | {"nail": 1000} # Python 3.9 and laterThe in operator tests keys, not values. To search values, use value in d.values().
Reading, adding, updating and removing entries
A hardware store's stock levels. Compare the two ways of reading a key that is not there, then watch the dictionary change.
stock = {"hinge": 40, "bolt": 250, "latch": 12}
print(stock["bolt"])
print(stock.get("washer"))
print(stock.get("washer", 0))
stock["washer"] = 500 # new key
stock["latch"] -= 2 # existing key
print(len(stock), "hinge" in stock)
del stock["hinge"]
sold = stock.pop("bolt")
print(sold)
print(stock)
print(list(stock.keys()), list(stock.values()))Output
250
None
0
4 True
250
{'latch': 10, 'washer': 500}
['latch', 'washer'] [10, 500]stock["bolt"] works because the key exists; stock["washer"] would have raised KeyError, so the program uses get, first with the default None and then with 0. Assigning to "washer" adds a fourth entry, while -= on "latch" updates one in place. pop returns the value it removed. The final print shows that the surviving entries keep their original relative order.
Counting with get
Lunch votes arrive one per line. Each vote either creates a key with count 1 or adds one to an existing count.
votes = {}
count = int(input())
for _ in range(count):
choice = input().strip()
votes[choice] = votes.get(choice, 0) + 1
for choice, n in sorted(votes.items(), key=lambda item: (-item[1], item[0])):
print(f"{choice}: {n}")Input given to the program: 6 ↵ soup ↵ pasta ↵ soup ↵ salad ↵ soup ↵ pasta
Output
soup: 3 pasta: 2 salad: 1
votes.get(choice, 0) returns the current count or 0 for a first appearance, so the same line handles both cases without an if. The report sorts the items with a key function that returns (-count, name): negating the count puts the largest first, and the name breaks ties alphabetically. collections.Counter in the standard library packages this counting pattern, but knowing the get form shows what it does.
Grouping with setdefault and nested dictionaries
Deliveries are grouped by district into lists, then a dictionary of dictionaries is read and updated two levels deep.
deliveries = [("north", "parcel"), ("east", "letter"), ("north", "letter"),
("south", "parcel"), ("north", "parcel")]
by_district = {}
for district, kind in deliveries:
by_district.setdefault(district, []).append(kind)
for district, kinds in by_district.items():
print(district, kinds)
rooms = {"101": {"guest": "Mora", "nights": 2},
"204": {"guest": "Chen", "nights": 5}}
rooms["204"]["nights"] += 1
print(rooms["204"])
print(rooms["101"]["guest"].upper())Output
north ['parcel', 'letter', 'parcel']
east ['letter']
south ['parcel']
{'guest': 'Chen', 'nights': 6}
MORAsetdefault(district, []) returns the existing list for that district or stores and returns a new empty one, so .append always has a list to work on. The groups print in the order the districts were first seen: north, east, south. In the nested structure, rooms["204"] is itself a dictionary, so a second pair of brackets reaches the nights value, and += updates it where it lives.
Ways to read a key
| Expression | Key present | Key missing |
|---|---|---|
| d[k] | the value | KeyError |
| d.get(k) | the value | None |
| d.get(k, 0) | the value | 0 |
| d.setdefault(k, []) | the value | stores [] under k and returns it |
| k in d | True | False |
Common mistakes
Reading a key that might not exist with square brackets
Why it goes wrong:
d[k]raisesKeyErrorfor a missing key, and data from files or users often lacks keys you expected.Fix: Use
d.get(k, default)or testk in dwhen absence is expected; keepd[k]where a missing key really would be a bug.Python · fixcount = totals.get(name, 0)Adding or deleting keys while looping over the dictionary
Why it goes wrong: Changing the size of a dictionary during iteration raises
RuntimeError: dictionary changed size during iteration.Fix: Loop over a copy of the keys with
for k in list(d), or build a new dictionary with a comprehension.Python · fixfor k in list(stock): if stock[k] == 0: del stock[k]Using a list as a key
Why it goes wrong: Keys must be hashable so lookups can use their hash; a list can change after insertion, so Python refuses it with
TypeError: unhashable type: 'list'.Fix: Use a tuple for compound keys:
seats[(row, number)] = name.Expecting d.items() to come back sorted
Why it goes wrong: Iteration follows insertion order, not key order. It only looks sorted when the keys happened to be inserted in sorted order.
Fix: Sort explicitly:
for k, v in sorted(d.items()), or passkey=lambda kv: kv[1]to sort by value.
Where you use this
Counting and grouping are the everyday jobs. Tallying how many times each word, status code or product appears is one dictionary and one get per item; splitting a flat list of records into groups by some field is one dictionary of lists and one setdefault per record. Both replace nested loops that would rescan the data for every distinct key.
Dictionaries are also the shape of structured data in transit. A JSON object from a web API becomes a dictionary, often with dictionaries and lists inside it, and a row from a CSV file is naturally a dictionary keyed by column name. Reading order["customer"]["email"] reads like the data it describes, which is why so many Python programs are dictionaries all the way down until the numbers come out.
totals = {}
for order in orders:
customer = order["customer"]
totals[customer] = totals.get(customer, 0) + order["amount"]Key points
- A dictionary maps unique, hashable keys to values with fast lookup by key.
d[k]raisesKeyErrorwhenkis missing;d.get(k, default)does not.- Assignment adds or replaces an entry;
delandpopremove one. - Iteration is in insertion order (Python 3.7 and later), not sorted order.
- Loop with
for k, v in d.items()to get both halves of each entry. - Use
getto count andsetdefaultto group without checking for the key first. - Never change a dictionary's size while iterating over it.
Try it yourself
The program looks up a station code typed by the user and crashes with KeyError for an unknown code. Change the lookup so an unknown code prints unknown station instead.
stations = {"BRD": "Bridge Road", "HLM": "Hill Market", "OLD": "Old Quay"}
code = input().strip().upper()
print(stations[code])zzzunknown stationstations = {"BRD": "Bridge Road", "HLM": "Hill Market", "OLD": "Old Quay"}
code = input().strip().upper()
print(stations.get(code, "unknown station"))Practise this
- Inventory movement tallyMedium
Aggregate stock movements by product and print a sorted Python dictionary report.
DictionariesNot started
- Word frequency reportMedium
Count repeated words with a Python dictionary and print a deterministic alphabetical frequency report.
DictionariesNot started
Related lessons
- Lists in PythonHow Python lists store ordered, changeable collections: creating them, indexing and slicing, adding and removing items, sorting, copying and looping over them.9 min
- Sets in PythonHow Python sets store unique, unordered items: removing duplicates, fast membership tests, union, intersection and difference, and why set order is unreliable.8 min
- Tuples in PythonHow Python tuples work: building fixed records, unpacking values into names, returning several results from a function and using tuples as dictionary keys.8 min
- Comprehensions in PythonHow list, dict and set comprehensions build a collection in one expression, with filters, transformations and nested loops, and when a loop is clearer.8 min
Frequently asked questions
Are Python dictionaries ordered?
Yes. Since Python 3.7 the language guarantees that a dictionary remembers insertion order, so iteration, keys(), values() and items() follow the order in which keys were first added, and reassigning an existing key keeps its position. This is insertion order, not sorted order; use sorted() when you need keys or values in order.
What is the difference between d.get(k) and d[k]?
d[k] returns the value or raises KeyError if the key is absent. d.get(k) returns the value or None, and d.get(k, default) returns your default instead. Use brackets when a missing key means a bug you want to see immediately, and get when a missing key is an ordinary case you plan to handle.
Can a dictionary have duplicate keys?
No. Each key appears once. Writing a literal with the same key twice keeps only the last value, and assigning to an existing key replaces its value rather than adding a second entry. If you need several values under one key, store a list as the value.
How this page was checked. Every program on it was run with CPython 3.11 at build time; runs in your browser on CPython 3.14 (Pyodide) by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.