Skip to content
The AI Agent Factory

13.4 Decisions and repetition

Approved by
Panaversity ·
19 min read

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 program has two moves, and the clerk makes both: decide, and repeat. This lesson takes them one at a time. Each one starts at the clerk's desk, and the notes beside the code say the clerk's sentence, line by line.

How do I find each decision?

Look for every place where the code chooses: an if, a filter, or an or. The sections below show each one.

At work. Ana's Chapter 12 script makes three decisions 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. She checks each one against the rule. The agent's first plan in Chapter 12 missed the second one.

How does code choose between two things?

With an if. The clerk's rule is one sentence: if the stamp says BILL, the envelope goes in the AP tray, and otherwise it goes in the general tray. The code is that sentence. Predict what each prints.

const stamp = "BILL";
if (stamp === "BILL") {         // if the stamp says BILL
  console.log("AP tray");       //   it goes in the AP tray
} else {                        // otherwise
  console.log("general tray");  //   it goes in the general tray
}

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

Each part of the clerk's sentence has a name in code.

  • The question is the condition. "Does the stamp say BILL?" has a yes-or-no answer. === in TypeScript and == in Python ask whether two values are equal.
  • "Otherwise" is else. Its lines run when the answer is no.
  • The lines that belong together are a block. TypeScript marks a block with braces { }. Python marks it by indentation, the spaces at the start of each line, so moving a line left or right changes which block it belongs to.

How does code do the same thing to every item?

With a loop. The clerk does not decide once and go home. The rule is "for each envelope in the tray, read the stamp". The clerk picks up one envelope, deals with it, and picks up the next, until the tray is empty. A for loop does this with a list.

Ana's tray is the list invoices. Each invoice is a short list of its own: its number, its status and its due date, as in 13.1. The name inv is the envelope in the clerk's hand. On each pass, it holds the next invoice. Predict how many lines print, and what the first one is.

const invoices = [
  ["INV-8301", "open", "2027-03-03"],
  ["INV-8309", "paid", "2027-02-28"],
  ["INV-8305", "open", ""],
];

for (const inv of invoices) {    // for each invoice in the tray
  console.log(inv[0], inv[1]);   //   print its number and status
}

Both print:

INV-8301 open
INV-8309 paid
INV-8305 open

The tray held three invoices, so the block ran three times. No line tells this loop to stop. It stops when the list runs out. In TypeScript, each pass makes a new inv, so const still fits.

How does code keep only some of the items?

With an if inside a loop, which is called a filter. It is the clerk's whole job in one sentence: for each envelope in the tray, if it is stamped BILL, put it in the AP tray. Ana wants only the open invoices, and inv[1] is the status. Predict which invoices print.

for (const inv of invoices) {    // for each invoice in the tray
  if (inv[1] === "open") {       //   if its status is open
    console.log(inv[0]);         //     print its number
  }
}

Both print:

INV-8301
INV-8305

INV-8309 is paid. Its answer was no, so nothing printed for it.

The agent's shorter form. Agents often write a filter in one line. In TypeScript it is invoices.filter((inv) => inv[1] === "open"). A dot means "this thing's own", and (inv) => ... means "for each inv, the answer is what follows the arrow". In Python it is a list comprehension: [inv for inv in invoices if inv[1] == "open"], read as "each inv in invoices, if its status is open". When you meet one, ask the agent which items it keeps, and check its answer against yours.

How does code ask two questions at once?

It joins two conditions with and or or. The clerk's rule could be "stamped BILL and addressed to Brightline", where both must be true. Or it could be "stamped BILL or stamped INVOICE", where either one is enough. Ana's rule needs and: an invoice is on Friday's list only if it is open and due on or before Friday. With or in that place, a paid invoice gets through.

"On or before" is a comparison of sizes. > asks whether a value is greater, and < whether it is less. >= and <= also say yes when the two are equal, so 5 > 5 is false and 5 >= 5 is true.

Only two of these marks differ between the languages.

TypeScriptPython
Equal=== (always use this one, not ==)==
And, or, not&&, `

What does code do with an empty value?

Whatever its rule says, and that is the trouble. The clerk's rule has a hole: a bill that nobody stamped goes in the general tray, and nobody pays it. The clerk did nothing wrong. The empty stamp slipped through the rule. Every decision in code has the same hole, so ask of each one what happens when the value is empty.

Ana's list has an empty value: INV-8305 has no due date. A careful clerk has a rule for that: "use the due date on the invoice, or, if it is blank, the one from the terms". Code says this with or. a || b in TypeScript, and a or b in Python, gives a, or b when a is empty. Predict both lines.

for (const inv of invoices) {
  if (inv[1] === "open") {
    const due = inv[2] || "from the terms";   // its due date, or this text if blank
    console.log(inv[0] + " " + due);
  }
}

Both print:

INV-8301 2027-03-03
INV-8305 from the terms

In a condition, the number 0, empty text "" and "nothing" all count as false. That is why the empty due date used the text after ||. It is also a trap: 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 stops when its list runs out, because the whole tray is there to see. A while loop is for work where nobody knows the count in advance. Say the mail comes in bags, and nobody says how many. The clerk's rule is "while the bag I just opened has mail in it, sort it and fetch the next bag". An empty bag ends the job. A clerk who forgets to fetch the next bag opens the same one forever.

Ana's bags are pages of invoices, read until one comes back empty. pages is a list of lists, one for each page, and pages[page] is the one at that position. Predict the last line.

const pages = [["INV-8301", "INV-8302"], ["INV-8306"], []];
let page = 0;
while (pages[page].length > 0) {   // while this page has invoices
  console.log("page", page + 1, "has", pages[page].length);
  page += 1;                       //   fetch the next page
}
console.log("stopped at page", page + 1);

Both print:

page 1 has 2
page 2 has 1
stopped at page 3

page += 1 adds 1 to page. It is the clerk fetching the next bag, the line that moves the loop toward its end. Every while loop needs a line like it, one that changes something its condition checks. Without one, the loop never ends. 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.

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.

Try it

The file in 13-4 is the invoice loop, with its if and its or. Run it and investigate, as in 13.1. Then write each decision in it as a sentence in your own words, such as "keep an invoice only if its status is open".

Modify. Send: "Please change INV-8301's status to Open, with a capital O. Don't run it yet." Use your sentence to predict which invoices it lists now. Then ask the agent to run it.

Make. Send: "Please make a new file in 13-4 called mine, with the same invoices, that lists only the paid ones. Don't run it yet." Read its if, say what it will list, and then ask the agent to run it.

What you should see. After your change, only INV-8305 from the terms is listed, because Open is not open. mine lists INV-8309.

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.

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

Next. A decision you need twice belongs in one place. 13.5 gives it a name.

Check yourself

Question 1 / 8

0 answered

The invoice loop uses inv[2] || "from the terms", where inv[2] is the due date. What does it print for INV-8305, whose due date is empty?

Owner
Panaversity