The list ["Mina", 3, 850] contains some booking information. Does 850 mean a ticket price, a total, or the number of ghosts in row B? Positional data becomes hard to read when its meaning lives only in your memory.
You need: lists, loops, and indexing. By the end: you can model a record, retrieve a field safely, and create a lookup by ID. Allow 25–35 minutes.
Keys describe the values
Save as booking_record.py:
booking = {
"id": "B001",
"customer": "Mina",
"tickets": 3,
"price_cents": 850,
}
print(booking["customer"])
print(booking["tickets"] * booking["price_cents"])
Output:
Mina
2550
A dictionary, or dict, maps keys to values. Curly braces create it; each colon connects a key to its value. Square brackets retrieve a value by key, not by position.
Keys must be unique within a dictionary. Assigning a new value to an existing key replaces its old value. For now, use strings or integers as keys; mutable lists and dictionaries cannot be dictionary keys.
Required fields and optional fields
booking = {"id": "B001", "tickets": 3}
booking["tickets"] = 4
booking["status"] = "confirmed"
print(booking["tickets"])
print(booking.get("note", "No note supplied"))
The output is 4 followed by No note supplied. booking["note"] would raise KeyError because that key is absent. .get() returns the supplied fallback instead; without a fallback it returns None.
Use direct lookup when a field is required and its absence should be investigated. Use .get() when absence is expected. Defaulting a missing ticket quantity to zero could conceal broken input and understate sales. “No error appeared” does not prove that a result is correct.
Work with several records
bookings = [
{"id": "B001", "customer": "Mina", "tickets": 3},
{"id": "B002", "customer": "Luca", "tickets": 2},
]
for booking in bookings:
print(f"{booking['customer']}: {booking['tickets']} tickets")
Expected output:
Mina: 3 tickets
Luca: 2 tickets
The outer list preserves a sequence of records. Each inner dictionary describes one record. Single quotes inside the double-quoted f-string keep the boundaries easy to see.
To inspect one record’s fields, use .items():
booking = {"id": "B001", "tickets": 3}
for field, value in booking.items():
print(f"{field}: {value}")
Looping directly over a dictionary yields its keys; .values() yields its values; .items() yields key/value pairs. Dictionaries preserve insertion order, but are not automatically sorted.
Build an index when repeated lookup matters
To locate one booking in a list, you might scan records until you find its ID. Doing that for every customer enquiry repeats the search. A dictionary keyed by ID provides a different access pattern:
bookings = [
{"id": "B001", "tickets": 3},
{"id": "B002", "tickets": 2},
]
by_id = {}
for booking in bookings:
booking_id = booking["id"]
if booking_id in by_id:
print(f"Duplicate ID rejected: {booking_id}")
else:
by_id[booking_id] = booking
print(by_id["B002"]["tickets"])
This prints 2. We explicitly reject duplicate IDs instead of silently overwriting an earlier booking. The values in by_id refer to the same records already in the list; this is not a deep copy.
Performance and memory choices
A list search may inspect every record. Dictionary lookup usually takes roughly constant time on average, rather than scanning all entries. Building the dictionary still takes time and adds memory overhead. For one search in a tiny list, creating an index may offer no benefit. For many searches against the same records, it can avoid substantial repeated work.
The bigger lesson is to choose a structure for the operations you actually need. An index is not free, and maintaining both a list and an index introduces consistency work if records are added or removed.
Your turn: totals by film
Given the following data, calculate ticket totals per film. The expected totals are 5 for Moon Mice and 1 for Quiet Zombies.
| Film | Tickets |
|---|---|
| Moon Mice | 2 |
| Quiet Zombies | 1 |
| Moon Mice | 3 |
Show the data and one solution
bookings = [
{"film": "Moon Mice", "tickets": 2},
{"film": "Quiet Zombies", "tickets": 1},
{"film": "Moon Mice", "tickets": 3},
]
totals = {}
for booking in bookings:
film = booking["film"]
totals[film] = totals.get(film, 0) + booking["tickets"]
print(totals)
Here the zero default is intentional: a film not yet seen has no accumulated tickets. That is different from inventing a default for a missing required input field. Try another film and check that no code change is needed.
Checkpoint: explain the difference between a list of records and a dictionary indexed by booking ID. State what you would do about duplicate IDs before building the index.