Python · Intermediate

Dictionaries in Python

9 min readUpdated September 24, 2026Every example verified

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

 Python · 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 later

The 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.

 Python
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.

 Python
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: 6souppastasoupsaladsouppasta

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.

 Python
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}
MORA

setdefault(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

ExpressionKey presentKey missing
d[k]the valueKeyError
d.get(k)the valueNone
d.get(k, 0)the value0
d.setdefault(k, [])the valuestores [] under k and returns it
k in dTrueFalse

Common mistakes

  • Reading a key that might not exist with square brackets

    Why it goes wrong: d[k] raises KeyError for a missing key, and data from files or users often lacks keys you expected.

    Fix: Use d.get(k, default) or test k in d when absence is expected; keep d[k] where a missing key really would be a bug.

     Python · fix
    count = 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 · fix
    for 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 pass key=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.

 Python · in practice
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] raises KeyError when k is missing; d.get(k, default) does not.
  • Assignment adds or replaces an entry; del and pop remove 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 get to count and setdefault to 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.

Your program
stations = {"BRD": "Bridge Road", "HLM": "Hill Market", "OLD": "Old Quay"}
code = input().strip().upper()
print(stations[code])
Input the program receives: zzz
Expected output: unknown station

Practise this

Open the Python playground

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.

Progress is stored only in this browser.

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.