# 13.6 Lists and dictionaries (/building-ai-workers/programming-fundamentals/lists-and-dictionaries)

---
type: Document
title: "13.6 Lists and dictionaries"
description: "What a lookup does with a missing key, what a sort compares, and the sets, tuples and slices that agent-written code uses."
status: stable
order: 313.06
ksor:
  owner: team:panaversity
  audience: [ public ]
  approval:
    by: process:panaversity
    at: 2026-11-02T08:11:58Z
chapter: "13"
part: III
expert_status: builder-only
concepts: [ "13.6" ]
last_verified: 2026-10-30
generated:
  at: 2026-11-02T08:11:58Z
  by: process:panaversity
trust_tier: unverified
build_id: sha256:e1fbcb7d9a8f8b9b7cb96875022325c02c9ba4be94cef13b301cbd39a9edad85
dirty: true
ksor_version: 0.0.60
---

**In everyday life.** A grocery **list** is in order: first milk, then bread. You find items by position. A coat check is different: you get your coat back by its ticket number, and a ticket they never gave out gets you nothing. Each has a trap: a list can come out in the wrong order, and a lookup can find nothing.

**What you will learn.** The two questions to ask of every sort and every lookup: what does it compare, and what if the name is missing?

**Why it matters.** A count and a total cannot see a wrong order or a missing name.

### List or dictionary?

A **list** is an ordered collection, where you find each item by its position. TypeScript calls it an **array**. A **dictionary** holds values under names, called **keys**, so you find a value by its key. TypeScript uses an **object**, written with braces, for the same job, and Python calls it a `dict`. A record, such as one invoice with its number, vendor and amount, is a dictionary too.

| | List (array) | Dictionary (object, dict) |
| --- | --- | --- |
| Found by | Position, starting at 0 | Key, such as `"V005"` |
| Brightline use | The open invoices, in order | Vendor names by vendor id |
| Written as | `["INV-8301", "INV-8305"]` | `{ V005: "Hocking Valley Pallets" }` |

**Records become JSON.** Most of the data a program sends or receives is a list of dictionaries, one for each record. [Chapter 14](../structuring-programs/json-and-rest-apis.md) reads invoices from an **API**, which is a way for one program to ask another for data. Each invoice arrives as a dictionary, in a text format called JSON. Reading this shape well helps in every later chapter.

### What does a sort compare, and why can't my count and total see it?

It compares what its rule tells it to compare. With no rule, it uses a default you must know. Sorting is where [Renee's script](overview.md#why-it-matters) went wrong, and the two languages behave differently. Read every sort line an agent writes, and ask what it compares.

**At work.** Ana still has the check she wrote in [Chapter 12](../agentic-coding/from-chat-to-coding-agent.md): Friday's 8 invoices, $19,229.53. The controller asks for Friday's list, largest first. The agent sorts it, the tests pass, and its summary matches her check. Before she has the agent run the list, she reads the one line that changed.

```ts
.sort((a, b) => String(b.cents).localeCompare(String(a.cents)));
```

The line turns each amount in cents into text with `String(...)`. Then `localeCompare` puts two pieces of text in order. She writes in `NOTES.md`: "It compares the amounts as text." As text, "61875" is larger than "482500", because its first character, 6, is larger than 4. Length does not count, just as "bus" comes after "airplane" in a dictionary. On Friday's list, only INV-8305's 735000 starts with a larger digit. So she predicts that INV-8302, Buckeye Janitorial Supply, at 61875 cents, will come second.

```text
$ npm run due -- 2027-03-05
2027-03-05  INV-8305  Hocking Valley Pallets  $7,350.00
2027-03-03  INV-8302  Buckeye Janitorial Supply  $618.75
2027-03-03  INV-8301  Midwest Packaging Co.  $4,825.00
2027-03-01  INV-8307  Ohio Valley Electric  $2,411.33
2027-02-26  INV-8304  Scioto Office Products  $234.50
2027-03-03  INV-8314  Lakefront Labels Inc.  $2,265.00
2027-03-05  INV-8312  Scioto Office Products  $199.95
2027-03-04  INV-8315  Cardinal Forklift Service  $1,325.00
8 invoices, total $19,229.53
```

It does. Her check named a count and a total, never an order, so only her sentence caught it. She lists amounts of different lengths, one of them seven digits, in the order they must come out. The agent writes that test, and she sees it fail on the old line. Then she has the agent compare the numbers, `b.cents - a.cents`, and the test passes. Next Friday's cutoff is `2027-03-12`. Left as it was, the line would put INV-8303, Great Lakes Freight's $12,900.00, the largest open invoice, below INV-8312's $199.95. It is [Renee's mistake](overview.md#why-it-matters), one week later.

### What happens to a missing key?

TypeScript gives `undefined` and keeps running. Python stops with a `KeyError`. A key must match exactly, capital letters too, so `v005` is not `V005`. A position past the end of a list is missing in the same way: `undefined` in TypeScript, an `IndexError` in Python.

**At work.** Brightline's export names each vendor by id, such as `V005`, not by name. To print names, the script builds a dictionary from `vendors.csv`, then looks up each invoice's vendor id. Ana asks one question about that code. What happens when an invoice names a vendor id that is not in the dictionary? [Chapter 14](../structuring-programs/errors-as-design.md)'s messy file has one, `V099`.

**In TypeScript.** `Record<string, string>` is an object whose keys and values are text. Predict the first three lines. The third one is the trap.

```ts
const names: Record<string, string> = { V001: "Midwest Packaging Co.", V005: "Hocking Valley Pallets" };
console.log(names["V005"]);
console.log(names["V099"]);
const cents = [735000, 482500, 61875];
console.log(cents.sort());
console.log(cents.sort((a, b) => a - b));
```

```text
Hocking Valley Pallets
undefined
[ 482500, 61875, 735000 ]
[ 61875, 482500, 735000 ]
```

A missing key gives `undefined`, with no error. And `sort()` with no rule compares the values as text, so 482500 comes before 61875. That is Renee's bug, in one line. The last line gives the rule, `(a, b) => a - b`. The sort hands the rule two items at a time, and a negative answer puts `a` first. For 61875 and 482500, `a - b` is -420625, so 61875 goes first. That makes `a - b` smallest first. `b - a` is largest first, as in Ana's `b.cents - a.cents`.

**In Python.**

```python
names: dict[str, str] = {"V001": "Midwest Packaging Co.", "V005": "Hocking Valley Pallets"}
print(names["V005"])
print(names.get("V099"))
cents: list[int] = [735000, 482500, 61875]
print(sorted(cents))
print(sorted(cents, key=str))
print(names["V099"])
```

```text
Hocking Valley Pallets
None
[61875, 482500, 735000]
[482500, 61875, 735000]
Traceback (most recent call last):
  ...
KeyError: 'V099'
```

`.get()` gives `None` for a missing key, or a default you pass it, as in `.get(key, 0)`. `sorted` compares numbers as numbers. Its rule goes in `key`, which says what to compare each item by, so `key=str` compares them as text and brings Renee's bug back on purpose. In agent code you will often see `key=lambda v: ...`. Read what follows the colon: that is what it compares. `reverse=True` runs a sort largest first.

### What are sets, tuples and slices?

Three more shapes you will see in agent code. Neither question above needs them, so read this table to recognize them when they appear.

| Shape | What it is | TypeScript | Python |
| --- | --- | --- | --- |
| A **set** | Holds each value once, with no positions, like a guest list. It finds a duplicate: the set comes out smaller than the list | `new Set(ids)`, `.has(x)`, `.size` | `set(ids)`, `x in s`, `len(s)` |
| A **tuple** | A short, fixed group of values, such as a vendor id and an amount | An array with a tuple type, `[string, number]`, which can still change | `("V005", 735000)`, which cannot change |
| A **slice** | Part of a list or a string, by position. From 0 to 2 takes positions 0 and 1, because the end is left out, like a hotel stay from the 1st to the 3rd: two nights. A negative number counts from the end | `slice(0, 2)`, `slice(-1)` | `[0:2]`, `[-1:]` |

In Python, `vendor_id, cents = pair` **unpacks** a tuple: it puts the tuple's two values into two variables. You will see this line often.

![Figure 13.7. Four invoice numbers at positions 0 to 3, with INV-8301 twice. A bracket marks the slice from 0 to 2 over positions 0 and 1, with position 2 marked as the end, left out. Another marks the last item, position 3. Below, the set holds three values with the duplicate dropped, and a tuple ("V005", 735000) that cannot be changed in Python.](img/sets-and-slices.png)

*Figure 13.7. A slice takes positions up to its end, never the end itself. A set keeps each value once. A tuple is fixed.*

**Where the languages differ.**

| | TypeScript | Python |
| --- | --- | --- |
| A missing key | `undefined`, and the program keeps running | `KeyError`, unless you use `.get()` |
| Sorting numbers with no rule | Compares them as text | Compares them as numbers |
| A sort's rule | `(a, b) => a - b` | `key=...`, and `reverse=True` for largest first |
| Total of a list | `reduce((sum, x) => sum + x, 0)`, a running total that starts at 0 | `sum(...)` |
| A new list from each item | `map((inv) => inv.cents)` | `[inv["cents"] for inv in invoices]` |

**The operating rule:** for every lookup, ask what happens when the key is missing, and for every sort, ask what it compares.

![Figure 13.8. The INV-8301 row of invoices.csv becoming a record. The header names become keys, the cells become values, and the amount "482500" is converted from text to the whole number 482500. Blank or invalid amounts are rejected.](img/row-to-record.png)

*Figure 13.8. A row becomes a record. The header gives the keys. Every value starts as text until the code converts it.*

**Practice it.** [Practice: lists and dictionaries](both-ai-vendors.md#practice-lists-and-dictionaries), in your own tool, fifteen minutes.
