Dictionaries
Look values up by name: the shape of almost all real-world data.
Why it matters
Real data is made of records with named fields. A campaign has a name, a budget and a status. A lead has an email and a source. Dictionaries are how Python holds a record. They matter even more for AI work. APIs send and receive data as dictionaries and lists inside each other. And every message you send to an LLM is a small dictionary: {"role": "user", "content": "..."}.
What you'll be able to do
- Store a record as key-value pairs and look values up by key
- Add, change and remove entries, and handle missing keys with .get()
- List a dictionary's keys, values and items
- Read and build nested data: lists of dictionaries, like an LLM chat request
What it covers
- Look things up by name
- Adding, changing and removing
- When a key might be missing
- Keys, values and items
- Nested data
- Worked example: an LLM chat request
- List or dictionary?
- Why keys need quotes
Look things up by name
Think of a contact card. You don't find someone's phone number by counting to the third line. You look for the label "phone". A list finds values by position. A dictionary (dict) finds them by key: a label you choose. Write it with curly brackets, with a colon between each key and its value:
campaign = {
"name": "Spring Sale",
"channel": "search",
"budget": 5000,
"spend": 3120.5,
}
print(campaign["name"])
print(campaign["budget"] - campaign["spend"])
print(len(campaign)) # number of key-value pairscampaign["budget"] reads as "the value stored under the key "budget"". That's far clearer than remembering that the budget lives at position 2 of a list.
Keys are usually strings, though numbers work too. Each key appears only once. Give an existing key a new value, and you replace the old one. Values can be anything, including lists and other dictionaries. A dictionary remembers the order its keys were added in.
Adding, changing and removing
The same square-bracket syntax reads, changes and adds:
campaign = {"name": "Spring Sale", "budget": 5000, "status": "paused"}
campaign["status"] = "active" # key exists: replace the value
campaign["clicks"] = 5470 # new key: add it
campaign["budget"] += 500 # update using the current value
del campaign["name"] # remove a key and its value
status = campaign.pop("status") # remove it and get the value back
print(campaign)
print(status)
campaign.update({"spend": 3120.5, "clicks": 5600}) # add or replace several at once
print(campaign)
Step through and watch the stock variable after each line: one key added, two values changed, one key removed. The last line reads a key that doesn't exist, safely.
When a key might be missing
Reading a key that doesn't exist with square brackets raises a KeyError:
lead = {"email": "dana@example.com", "source": "webinar"}
print(lead["phone"])
Data from forms and APIs often leaves fields out, so you need gentler options:
"phone" in leadasks whether the key exists and givesTrueorFalse. (inchecks keys, not values.)lead.get("phone")returns the value if the key exists, andNoneif it doesn't.lead.get("phone", "unknown")returns your default instead ofNone.
lead = {"email": "dana@example.com", "source": "webinar"}
print("phone" in lead)
print(lead.get("phone"))
print(lead.get("phone", "unknown"))
print(lead.get("source", "unknown"))
.get() with a default also gives you a neat way to count:
signups_by_source = {"webinar": 12, "ads": 30}
signups_by_source["email"] = signups_by_source.get("email", 0) + 1 # new key starts at 0
signups_by_source["ads"] = signups_by_source.get("ads", 0) + 1 # existing key adds 1
print(signups_by_source)
prices = {"tee": 28, "mug": 18}
print(prices.get("cap", 0))Show the answer
0
There's no 'cap' key, so .get() returns the default you gave it, 0. Square brackets would have raised a KeyError.
Keys, values and items
Three methods show you what a dictionary holds. Wrap them in list() to see them as ordinary lists:
spend = {"search": 3120.5, "display": 1890.0, "email": 120.0}
print(list(spend.keys()))
print(list(spend.values()))
print(list(spend.items()))
print("Total:", sum(spend.values()))
print("Channels A-Z:", sorted(spend)) # sorting a dict sorts its keys
Each item is a pair in parentheses, such as ('search', 3120.5). A pair like that is a tuple: a list you can't change. To go through every key or item one at a time, you'll use a loop. The comprehensions lesson shows the patterns for that.
lead = {"name": "Ana", "email": "ana@shop.com"}
print(lead.get("phone", "none given"))Show the answer
none given
The key "phone" isn't in lead, so .get() returns the default you gave it, and that's what prints: none given. lead["phone"] with square brackets would raise a KeyError instead.
Nested data
Values can be lists or other dictionaries, and lists can hold dictionaries. This "nesting", like boxes inside boxes, is how almost all data from APIs looks. Read nested values by chaining lookups from left to right, one step at a time:
report = {
"account": "Index Demo Shop",
"campaigns": [
{"name": "Spring Sale", "tags": ["search", "seasonal"], "clicks": 5470},
{"name": "Retargeting", "tags": ["display"], "clicks": 2050},
],
}
print(report["campaigns"][1]["name"]) # 2nd campaign's name
print(report["campaigns"][0]["tags"][-1]) # 1st campaign's last tag
print(len(report["campaigns"]))
Click around the tree below. Each value shows the exact Python expression that reaches it.
Worked example: an LLM chat request
A request to a chat model is a dictionary. Its messages key holds a list of dictionaries, one per turn of the conversation. Build one, add the model's reply and a follow-up, then read pieces back out.
request = {
"model": "claude-opus-5-5",
"max_tokens": 300,
"messages": [
{"role": "user", "content": "Suggest a subject line for our spring sale."},
],
}
reply = {"role": "assistant", "content": "Spring into savings: 30% off this week"}
request["messages"].append(reply)
request["messages"].append({"role": "user", "content": "Make it shorter."})
print(len(request["messages"]), "messages")
print("Last speaker:", request["messages"][-1]["role"])
print("First answer:", request["messages"][1]["content"])
print("System prompt:", request.get("system", "(none)"))This is the real shape of a request to a chat model's API: a model name, a token limit and the conversation so far, as a list of role/content dictionaries. You'll send one for real in the "first LLM call" lesson. request["messages"] is a list, so .append() adds a turn to it. request["messages"][-1]["role"] is three lookups: the messages list, its last item, that item's role. .get() with a default handles the optional system key, which this request doesn't have.
data = {"items": [{"sku": "TEE-001"}, {"sku": "MUG-003"}]}. Which expression gives "MUG-003"?Show the answer
data["items"][1]["sku"]
Work outside in: the "items" key gives a list, [1] picks its second dictionary, and ["sku"] reads that dictionary's sku.
List or dictionary?
Both hold many values. The difference is how you find one again.
Use a list when order matters and you'll go through the items one by one: the steps of a recipe, or a day's orders. Use a dictionary when you want to look something up by name: a customer by their email, or a price by its product code.
The speed difference is real too. To find one customer in a list of a million, Python may have to check a million items. A dictionary goes almost straight there, however big it gets. You'll learn why in Phase 7.
A pattern worth knowing: counting
Dictionaries are the natural way to count things. For each item, add one to its count, starting from zero if you haven't seen it before.
words = ["sale", "boots", "sale", "coat", "sale", "boots"] counts = {} for word in words: counts[word] = counts.get(word, 0) + 1 print(counts)
.get(word, 0) means "give me the current count, or 0 if there isn't one". You'll use this shape constantly, for tallying clicks, labels or errors.
Walking through nested data
Real data is often a dictionary holding lists holding dictionaries. When it looks tangled, go one step at a time, and print as you go.
order = {"id": 7, "items": [{"sku": "TENT", "qty": 1}, {"sku": "MUG", "qty": 2}]}
print(order["items"]) # the list
print(order["items"][1]) # its second item
print(order["items"][1]["sku"])
If you get a KeyError or a TypeError here, the last thing you printed tells you what you really have. Writing order["sku"][1] fails because the order itself has no sku. Follow the shape of the data from the outside in.
Two small slips to watch for. "TENT" in order asks about keys, not values. And order[id] without quotes looks for a variable called id. Keys that are text need their quotes.
Why keys need quotes
A key is a value like any other. order["id"] looks up the text id. order[id] looks up whatever the variable id holds, and if there's no such variable, Python stops with a NameError. When your keys are text, they need quotes every time. Keys can be numbers too, which is why prices[7] without quotes is fine. One more thing: a dictionary can't have the same key twice. Setting an existing key replaces its value, without warning, so check first if that really matters.
Practice what you just read
- 10 code examples you can edit and run
- a practice exercise that checks your code
- a 6-question quiz
- 5 review cards for spaced repetition
Try a quiz question
What happens when you run this?
stock = {"tee": 140}
print(stock["hoodie"])