Index Ai Academy
Phase 0 · Tools · Python core

Dictionaries

Look values up by name: the shape of almost all real-world data.

About 25 minutes · Free lesson

Start this lesson

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

  1. Look things up by name
  2. Adding, changing and removing
  3. When a key might be missing
  4. Keys, values and items
  5. Nested data
  6. Worked example: an LLM chat request
  7. List or dictionary?
  8. 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 pairs
It runs right here in your browser, with nothing to install.

campaign["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)
Try it now

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 lead asks whether the key exists and gives True or False. (in checks keys, not values.)
  • lead.get("phone") returns the value if the key exists, and None if it doesn't.
  • lead.get("phone", "unknown") returns your default instead of None.
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)
Quick check
What does this print?
prices = {"tee": 28, "mug": 18}
print(prices.get("cap", 0))
  1. An error: KeyError
  2. 18
  3. None
  4. 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.

Quick check
What does this print?
lead = {"name": "Ana", "email": "ana@shop.com"}
print(lead.get("phone", "none given"))
  1. None
  2. phone
  3. none given
  4. An error (KeyError)
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

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)"))
In the lesson you can change this and run it.

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.

Quick check
data = {"items": [{"sku": "TEE-001"}, {"sku": "MUG-003"}]}. Which expression gives "MUG-003"?
  1. data[1]["items"]["sku"]
  2. data["items"][1]["sku"]
  3. data["items"]["sku"][1]
  4. data["sku"][1]
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

Start this lesson

Try a quiz question

What happens when you run this?

stock = {"tee": 140}
print(stock["hoodie"])
  1. It raises a KeyError
  2. It prints 0
  3. It prints None
  4. It prints an empty line

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson