BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

PY-01-009-P · Primer — Dictionaries

The idea in one sentence

A dictionary (dict) stores data as key → value pairs — you look things up by a meaningful name like "apples" instead of a numeric position — giving you instant answers to "what is the value for this thing?"

Why it exists

A list is great when order is the point and you fetch items by position (prices[0]), but a huge amount of real data is really a lookup table: a username to a password, a product to its price, a country to its capital, a word to its count. Storing that in a list would force you to remember that "apples lives at index 3" and to scan the whole list every time you wanted its value — tedious and slow. A dictionary flips the model: you attach each value to a key you choose, then ask for the value by that key directly, with no scanning and no memorized positions. It exists because "map this label to that data" is one of the most common shapes information takes, and Python makes it a first-class, blazingly fast built-in. Under the hood it uses a hash table, which is why looking up a key stays fast even when the dictionary holds millions of entries.

How it works

You write a dict with curly braces and key: value pairs, and you read a value by putting its key in square brackets — inventory["apples"]. To insert a brand-new pair or update an existing one, you assign to a key: inventory["bananas"] = 7 adds it, and assigning to a key that already exists overwrites the old value. Keys must be unique and unchangeable (strings and numbers are the usual choices); values can be anything. To visit every pair, loop over .items(), which hands you the key and value together on each pass — and .keys() and .values() give you just one side when that's all you need. The safe way to read a key that might be missing is .get(key, default), which returns your fallback instead of crashing. Here it all is in one runnable example:

inventory = {"apples": 3, "pears": 12}   # a dict of two key -> value pairs

print(inventory["apples"])        # 3      look up a value by its key
inventory["bananas"] = 7          # insert a NEW key
inventory["apples"] = 5           # update an EXISTING key (overwrite)
print(inventory)                  # {'apples': 5, 'pears': 12, 'bananas': 7}

print(inventory.get("kiwis", 0))  # 0      missing key -> safe default
print("pears" in inventory)       # True   membership test checks the KEYS

for fruit, count in inventory.items():   # iterate over pairs
    print(fruit, "->", count)
# apples -> 5
# pears -> 12
# bananas -> 7

The one gotcha

Reading a missing key with square brackets raises KeyError and stops your program — .get() does not. Beginners reach for inventory["kiwis"] out of habit, forgetting that unlike a list (where an out-of-range index is at least an obvious mistake), a dictionary key is easy to typo or to simply not have yet:

prices = {"coffee": 3}
print(prices["tea"])       # KeyError: 'tea'   -> crash
print(prices.get("tea"))   # None    -> no crash, returns the default (None)
print(prices.get("tea", 0))# 0       -> supply your own fallback

The rule: use dict[key] only when you're certain the key exists; when it might not, use .get(key, default) to hand back a sensible fallback, or guard with if key in dict: first. This one habit — reaching for .get() with a default — prevents a whole category of crashes and is the mark of code that handles real, messy data gracefully.

See it — choosing a safe path to a value

flowchart TD
    A["Need the value<br/>for a key"] --> B{"Sure the key<br/>exists?"}
    B -->|"Yes"| C["Use dict[key]<br/>fast, direct"]
    B -->|"Not sure"| D{"Want a fallback<br/>if missing?"}
    D -->|"Yes"| E["Use dict.get(key,<br/>default)"]
    D -->|"No, just check"| F["Use key in dict<br/>then decide"]
    C --> G["Get the value"]
    E --> G
    F --> G
    C -.->|"key absent"| H["KeyError: crash"]

The one-line takeaway

A dict maps keys to values for instant lookup; assign to a key to insert or update it, loop with .items() to see every pair, and reach for .get(key, default) whenever a key might be missing.

Where you'll meet it

Dictionaries are everywhere the moment you leave toy examples. Every JSON response from a web API arrives as nested dicts, so data.get("user", {}).get("name") is how you safely dig out a field that may not be there. Counting things — words in a document, clicks per page, votes per candidate — is the classic dict pattern: counts[word] = counts.get(word, 0) + 1 in a loop, where .get(..., 0) gracefully starts each new key at zero. Configuration settings, database rows, function keyword arguments, and caches are all dicts under the hood. And the .get()-with-default habit you learn here scales straight into professional code, where handling a missing key calmly instead of crashing is the difference between a program that survives real data and one that falls over on the first surprise.

Named concepts: key-value mapping, lookup, insertion, and update, keys/values/items iteration, get and default handling