# 13.4 Decisions and repetition (/building-ai-workers/programming-fundamentals/decisions-and-repetition)

---
type: Document
title: "13.4 Decisions and repetition"
description: "How to find each decision and loop in agent-written code, say it as a sentence, and catch the empty value that slips through."
status: stable
order: 313.04
ksor:
  owner: team:panaversity
  audience: [ public ]
  approval:
    by: process:panaversity
    at: 2026-11-02T08:11:57Z
chapter: "13"
part: III
expert_status: builder-only
concepts: [ "13.4" ]
last_verified: 2026-10-30
generated:
  at: 2026-11-02T08:11:57Z
  by: process:panaversity
trust_tier: unverified
build_id: sha256:e1fbcb7d9a8f8b9b7cb96875022325c02c9ba4be94cef13b301cbd39a9edad85
dirty: true
ksor_version: 0.0.60
---

**In everyday life.** A mail clerk sorts the morning mail. Each envelope stamped BILL goes in the AP tray. Every other envelope goes in the general tray, even a bill that nobody stamped. The clerk repeats one small decision for every envelope. That is most of what a program does too.

**What you will learn.** How to find each decision and each repeated step, and say it as a sentence.

**Why it matters.** Each decision is a rule, and an empty value can slip through one.

A **condition** is a yes-or-no question about a value, such as whether the status is open. An **if** runs some lines only when its condition is true, and an **else** runs others when it is false. A **loop** repeats lines once for each item in a collection. Two words join conditions. With **and**, both must be true. With **or**, either one is enough, and `a or b` gives `b` when `a` is empty.

| Shape | Reads as | Brightline use |
| --- | --- | --- |
| `if ... else ...` | Do this, or that | An open invoice is listed, a paid one is skipped |
| A loop over items | For each invoice, do this | Print one line per invoice |
| A filter | Keep only the items whose answer is yes | Keep the open invoices |
| `a or b` | Use `a`, or use `b` if `a` is empty | Stated due date, or the one computed from the terms |
| `while` | Repeat while a condition stays true | Fetch pages of invoices until one comes back empty |

### How do I find each decision?

Look for every place where the code chooses: an `if`, a filter, or an `or`.

**At work.** Ana's Chapter 12 script does three things in a row for every invoice. It skips the invoice unless it is open. It takes the invoice's due date, or computes one from the terms. And it keeps the invoice if the due date is on or before Friday. Reading the script means finding those three decisions and checking each one against the rule. The agent's first plan in [Chapter 12](../agentic-coding/permissions-and-plan.md) missed the second one.

**The plainest decision is an `if`.** Here is the mail clerk's rule, in both languages. `===` in TypeScript and `==` in Python ask whether two values are equal. Predict what each prints.

```ts
const stamp = "BILL";
if (stamp === "BILL") {
  console.log("AP tray");
} else {
  console.log("general tray");
}
```

```python
stamp = "BILL"
if stamp == "BILL":
    print("AP tray")
else:
    print("general tray")
```

Both print `AP tray`. With any other stamp, both print `general tray`.

**A filter asks the same question of every item.** In TypeScript, each invoice is written in braces, as names with their values. The filter keeps each invoice, `inv`, whose `status` is `"open"`. Its rule is written in place with an arrow: `(inv) => ...` means "for each `inv`, the answer is what follows the arrow". `||` is TypeScript's or. `Boolean(...)` shows whether a value counts as true or false. Predict both lines of the loop, then the last line.

```ts
const invoices = [
  { number: "INV-8301", status: "open", due: "2027-03-03" },
  { number: "INV-8309", status: "paid", due: "2027-02-28" },
  { number: "INV-8305", status: "open", due: "" },
];
const open = invoices.filter((inv) => inv.status === "open");
for (const inv of open) {
  const due = inv.due || "from the terms";
  console.log(inv.number, due);
}
console.log(Boolean(""), Boolean([]));
```

```text
INV-8301 2027-03-03
INV-8305 from the terms
false true
```

**In Python.** The filter here is a **list comprehension**: a list built from another list in one line, read as "each inv in invoices, if its status is open". The label `list[dict[str, str]]` says it is a list of records, each with text names and text values.

```python
invoices: list[dict[str, str]] = [
    {"number": "INV-8301", "status": "open", "due": "2027-03-03"},
    {"number": "INV-8309", "status": "paid", "due": "2027-02-28"},
    {"number": "INV-8305", "status": "open", "due": ""},
]
open_invoices = [inv for inv in invoices if inv["status"] == "open"]
for inv in open_invoices:
    due = inv["due"] or "from the terms"
    print(inv["number"], due)
print(bool(""), bool([]))
```

```text
INV-8301 2027-03-03
INV-8305 from the terms
False False
```

### Why can an empty value slip through a decision?

Some values count as false, and not all of them are empty. In a condition, both languages treat some values as false even though they are not `false`: the number 0, empty text `""`, and "nothing". That is why `due || computed` works. An empty due date counts as false, so the computed one is used. It is also a trap. An amount of 0 counts as false too, so a condition meant to ask "is there an amount?" would treat a zero-dollar credit as missing.

### How does a loop stop?

A `for` loop runs once for each item you already have. A **`while` loop** repeats as long as its condition stays true. It is for work where you do not know the count in advance, like a clerk opening mailbags until one comes empty, or reading pages of invoices until one comes back empty. Every `while` loop needs a way to stop. That is a line that changes something its condition checks, or a `break` or `return` that leaves the loop. Without one, the loop never ends. `pages` is a list of lists, one for each page, and `pages[page]` is the one at that position. `let` makes a variable that can change, and `.length` in TypeScript and `len()` in Python count a list's items. Predict the last line.

**In TypeScript.**

```ts
const pages = [["INV-8301", "INV-8302"], ["INV-8306"], []];
let page = 0;
while (pages[page].length > 0) {
  console.log("page", page + 1, "has", pages[page].length);
  page += 1;
}
console.log("stopped at page", page + 1);
```

**In Python.**

```python
pages = [["INV-8301", "INV-8302"], ["INV-8306"], []]
page = 0
while len(pages[page]) > 0:
    print("page", page + 1, "has", len(pages[page]))
    page += 1
print("stopped at page", page + 1)
```

Both print:

```text
page 1 has 2
page 2 has 1
stopped at page 3
```

`page += 1` adds 1 to `page`, and it is the line that moves the loop toward its end. Positions count from 0, so the code prints `page + 1`.

![Figure 13.4. A while loop reading pages. Page 1 holds INV-8301 and INV-8302 and page 2 holds INV-8306, so each time the loop prints and moves on with page += 1. Page 3 is empty, so the condition is false and the loop stops. Without page += 1, it would fetch the same page again and again.](img/while-loop.png)

*Figure 13.4. A while loop. Each pass checks the condition again, and page += 1 is what moves it toward the empty page that stops it.*

**Where the languages differ.**

| | TypeScript | Python |
| --- | --- | --- |
| Equal | `===` (always use this one, not `==`) | `==` |
| And, or, not | `&&`, `||`, `!` | `and`, `or`, `not` |
| An empty list in a condition | Counts as true | Counts as false |
| Repeat while true | `while (cond) { ... }` | `while cond:` |

The row about an empty list causes real bugs. "If there are any errors, stop" written as `if (errors)` in TypeScript is always true, even when the list of errors is empty. Check the length instead: `errors.length > 0`.

**The operating rule:** find every decision in the code and say it as a sentence, then check each sentence against the business rule, including what happens to empty values. For every `while` loop, find the line that makes it stop.

![Figure 13.5. Four invoice rows through a filter for Friday, March 5, 2027. INV-8301 is open and due March 3, so it stays. INV-8309 is paid, so it leaves at the first check. INV-8305 has no due date, so Net 30 gives March 5, and it stays. INV-8303 is due March 6, after Friday, so it leaves.](img/row-through-filter.png)

*Figure 13.5. A row through a filter. Each gate is one condition.*

**Practice it.** [Practice: decisions and loops](both-ai-vendors.md#practice-decisions-and-loops), in your own tool, ten minutes.
