---
title: 'Iteration Constructs'
source: 'https://academia.sh/en/courses/infrastructure-as-code/iteration-constructs'
course: 'Infrastructure as Code'
language: en
updated: '2026-09-13T05:08:23+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Writes the same ten resources in three forms and counts the record's cost: lines, conditional branches, address readability"
    - "Compares the count-based and set-based forms' plan items when an element is removed from the middle"
    - "Counts the shifted address's counterpart on the measurement network as missed meter-reading batches"
---

# Iteration Constructs

The same ten resources are written in three record forms: ten blocks by hand, count-based iteration, set-based iteration. Line count, the cost of an exception, and address stability are counted; it measures that the count-based form produces a six-item plan when a subregion is removed from the middle, and that five shifted addresses correspond to thirty-five missed meter-reading batches on the measurement network.

The previous lesson's record had two collector machines, written separately as two blocks, four
lines identical letter for letter. The region has grown: the measurement network now reads meters
in ten separate subregions. Copying the same block ten times is one option; telling the record
itself "do this ten times" is another. This lesson adds a third option and compares all three on
the same items.

The comparison's starting point is this: **all three forms produce the same reality.** Ten
machines, the same subnets, the same sizes, the same tags. The difference between them is not in
reality, it is in the record and in the **address** the record gives a resource. That address is the
only thing that determines which real resource the record means on the next apply — and every
number in this lesson comes from there.

The measurement runs on the **regional measurement network** and is **fictional**; what runs is a
**model** built with `node`.

**DP31.** The measurement network reads meters in ten subregions; the subregion names are in the
order written in the list.
**DP32.** All three record forms define the same ten machines; their attributes are identical.
**DP33.** The address is the key under which a resource sits in state: a block name in the manual
form, an index in the count-based form, a map key in the set-based form.
**DP34.** One subregion carries twice the meters and needs a large machine; this is the only
deviation placed here to measure how the forms carry an exception.
**DP35.** The `subnet` and `tag` fields cannot be updated in place; if they change, the resource is
recreated. `size` updates in place.
**DP36.** Every collector pulls a meter-reading batch every 15 seconds. A recreated machine, with
the previous lesson's durations, is out of service for 105 seconds.

## The Same Ten Resources, Three Records

```js
// iteration.mjs — ten resources written in three forms; lines, address stability, and the plan are counted. Model.
export const SUBREGION = ["north_1", "north_2", "north_3", "east_1", "east_2",
  "east_3", "west_1", "west_2", "west_3", "west_4"];
const EXCEPTION = { east_2: "large" };            // DP34: one subregion carries twice the meters
const attributes = (b, exc) => ({ subnet: `subnet.${b}`, size: exc[b] ?? "medium",
  tag: `collector-${b}` });

export function manual(subregions, exc = EXCEPTION) {
  const text = subregions.map((b) => `resource machine "collector_${b}" {\n` +
    `  subnet = subnet.${b}.id\n  size   = "${exc[b] ?? "medium"}"\n` +
    `  tag    = "collector-${b}"\n}`).join("\n");
  const resolved = new Map(subregions.map((b) => [`machine.collector_${b}`, attributes(b, exc)]));
  return { text, resolved, branches: 0 };               // value in its own block, no condition
}

export function countBased(subregions, exc = EXCEPTION) {
  const branches = Object.entries(exc).map(([k, v]) =>
    `subregions[count.index] == "${k}" ? "${v}" : `).join("");
  const text = `local subregions = [${subregions.map((b) => `"${b}"`).join(", ")}]\n` +
    `resource machine "collector" {\n  count  = ${subregions.length}\n` +
    `  subnet = subnet[subregions[count.index]].id\n  size   = ${branches}"medium"\n` +
    `  tag    = "collector-" + subregions[count.index]\n}`;
  const resolved = new Map(subregions.map((b, i) => [`machine.collector[${i}]`, attributes(b, exc)]));
  return { text, resolved, branches: Object.keys(exc).length };
}

export function setBased(subregions, exc = EXCEPTION) {
  const text = "local subregions = {\n" +
    subregions.map((b) => `  ${b} = "${exc[b] ?? "medium"}"`).join("\n") + "\n}\n" +
    `resource machine "collector" {\n  set    = subregions\n` +
    "  subnet = subnet[set.key].id\n  size   = set.value\n" +
    '  tag    = "collector-" + set.key\n}';
  const resolved = new Map(subregions.map((b) => [`machine.collector["${b}"]`, attributes(b, exc)]));
  return { text, resolved, branches: 0 };               // exception lives in the map's value
}

const FORCES_RECREATE = ["subnet", "tag"];            // DP35: these fields cannot update in place

export function plan(before, after) {
  const p = { add: 0, update: 0, recreate: 0, delete: 0 };
  for (const [address, y] of after.resolved) {
    const e = before.resolved.get(address);
    if (!e) { p.add++; continue; }
    const diff = Object.keys(y).filter((k) => e[k] !== y[k]);
    if (diff.length) diff.some((k) => FORCES_RECREATE.includes(k)) ? p.recreate++ : p.update++;
  }
  for (const address of before.resolved.keys()) if (!after.resolved.has(address)) p.delete++;
  return p;
}

export const FORM = [["manual, ten blocks", manual], ["count-based", countBased],
  ["set-based", setBased]];
const lineCount = (m) => m.split("\n").length;
const THREE = { east_2: "large", west_1: "large", north_3: "small" };

if (import.meta.url === `file://${process.argv[1]}`) {
  // Do all three forms produce the same ten real resources: compare attributes, not the address.
  const signature = (c) => [...c.values()].map((v) => JSON.stringify(v)).sort().join("|");
  const signatures = FORM.map(([, f]) => signature(f(SUBREGION).resolved));
  console.log("do all three forms produce the same reality: " +
    `${signatures.every((x) => x === signatures[0]) ? "yes" : "no"} ` +
    `(${manual(SUBREGION).resolved.size} resources, same attributes)`);

  console.log("\nthe record's cost (10 resources)");
  console.log(`${"form".padEnd(21)}${"lines".padStart(7)}${"cond branch".padStart(13)}${"lines/branch at 3 exc".padStart(24)}${"subregion in address".padStart(23)}`);
  for (const [name, f] of FORM) {
    const r = f(SUBREGION), u = f(SUBREGION, THREE);
    const addressed = [...r.resolved.keys()].some((a) => a.includes("east_2"));
    console.log(`${name.padEnd(21)}${String(lineCount(r.text)).padStart(7)}${String(r.branches).padStart(13)}` +
      `${`${lineCount(u.text)}/${u.branches}`.padStart(24)}${(addressed ? "yes" : "no").padStart(23)}`);
  }

  const SCENARIO = [
    ["a subregion drops out of the middle (east_2 closes)", SUBREGION.filter((b) => b !== "east_2")],
    ["a subregion is inserted in the middle (east_1b opens)", [...SUBREGION.slice(0, 4), "east_1b", ...SUBREGION.slice(4)]],
    ["a subregion is appended at the end (west_5 opens)", [...SUBREGION, "west_5"]],
  ];

  for (const [name, next] of SCENARIO) {
    console.log(`\n${name}`);
    console.log(`${"form".padEnd(21)}${"add".padStart(5)}${"update".padStart(8)}${"recreate".padStart(10)}${"delete".padStart(8)}${"total items".padStart(13)}${"shifted address".padStart(18)}`);
    for (const [formName, f] of FORM) {
      const before = f(SUBREGION), after = f(next), p = plan(before, after);
      const shifted = [...after.resolved].filter(([a, v]) =>
        before.resolved.has(a) && JSON.stringify(before.resolved.get(a)) !== JSON.stringify(v)).length;
      console.log(`${formName.padEnd(21)}${String(p.add).padStart(5)}${String(p.update).padStart(8)}` +
        `${String(p.recreate).padStart(10)}${String(p.delete).padStart(8)}` +
        `${String(p.add + p.update + p.recreate + p.delete).padStart(13)}${String(shifted).padStart(18)}`);
    }
  }
}
```

```
do all three forms produce the same reality: yes (10 resources, same attributes)

the record's cost (10 resources)
form                   lines  cond branch   lines/branch at 3 exc   subregion in address
manual, ten blocks        50            0                    50/0                    yes
count-based                7            1                     7/3                     no
set-based                 18            0                    18/0                    yes

a subregion drops out of the middle (east_2 closes)
form                   add  update  recreate  delete  total items   shifted address
manual, ten blocks       0       0         0       1            1                 0
count-based              0       0         5       1            6                 5
set-based                0       0         0       1            1                 0

a subregion is inserted in the middle (east_1b opens)
form                   add  update  recreate  delete  total items   shifted address
manual, ten blocks       1       0         0       0            1                 0
count-based              1       0         6       0            7                 6
set-based                1       0         0       0            1                 0

a subregion is appended at the end (west_5 opens)
form                   add  update  recreate  delete  total items   shifted address
manual, ten blocks       1       0         0       0            1                 0
count-based              1       0         0       0            1                 0
set-based                1       0         0       0            1                 0
```

The first line is the comparison's foundation: all three forms produce the ten resources with the
same attributes. The record's form does not change reality.

The lines column gives the first cost. The manual form is 50 lines, set-based is 18, count-based
is 7. 40 of the manual form's 50 lines are repetition: the same three fields ten times, only the
subregion name changing. The count-based form is shortest because it packs the list into a single
line. The set-based form sits in between, and its extra length is not an accident — every line of
the map writes a subregion by its name and its value.

The conditional branch column gives the exception's price. A subregion wanting a large machine is,
in the manual form, a value written on that block's own line; in the set-based form, the value at
that key in the map; both carry zero conditions. In the count-based form, though, a branch enters
the expression. Looking from the last column to the one before it shows how this grows: when the
exception count rises to three, the line and branch counts do not change in the manual and
set-based forms, but in the count-based form, three nested conditions pile up in a single line. In
the count-based form, the place that carries the exception is not the record's data, it is its
expression.

The last column, though, is this lesson's real item. In the manual and set-based forms, the address
carries the subregion name: `machine.collector_east_2` or `machine.collector["east_2"]`. In the
count-based form, the address is `machine.collector[4]`, and which subregion the fourth position
belongs to is not written in the address — that information lives in the list's state **at apply
time**. To look at a resource in state and say which subregion it belongs to, you have to find that
day's version of the record.

What the address is looks like a detail, but in this course's measure it is a central item. When the
record is applied, every resource's real identity is written under an address; on the next apply,
that address is the only thing that matches the record to reality. If the address carries the
resource's identity, the match can be read from the record itself; if it carries an index, the
match depends on the record's order **that day**. In the second case, the link between record and
reality rests on something not written in the record at all — the list's past state.

## When an Element Drops Out of the Middle

Three scenarios are subsections of the same output, and reading them together makes the rule
plain.

When subregion `east_2` closes, the manual and set-based forms produce a **single-item
plan**: one resource is deleted, the other nine stay in place untouched. The count-based form
produces **six items** for the same real change: five recreations and one delete. The reason is
arithmetic. When the list loses its fourth element, the fifth element shifts to the fourth
position, the sixth to the fifth; the address `machine.collector[4]` pointed to `east_2` yesterday,
today it points to `east_3`. The record says "the fourth machine's subnet and tag changed"; since
these two fields cannot be updated in place, the decision is recreation. The shifted address column
counts this directly: five addresses now point to a different real resource.

Inserting a subregion in the middle is even more expensive: seven items, six shifted addresses.
Every address after the insertion point shifts by one.

The third scenario draws the boundary. When a subregion is appended to the end of the list, all
three forms produce a single item; the count-based form is clean too, because no address shifts.
So count-based iteration is not wrong — it is the correct form for sets whose order carries no
meaning and that only grow from the end. The subregion list is not that kind of set: subregions
close, and new ones get inserted in the middle.

Here, the plan's predictive accuracy asks an inverse question. The plan is not wrong — in the
count-based form it says "five resources will be recreated," and apply recreates exactly five, a
ratio of 5/5. What is wrong is not the plan, it is the expectation of the person reading it: one
subregion closed, one machine should be gone. The expectation is one item, the plan is six. The
five items in between are born not from reality but from the **record's form**.

That the record cannot tell this apart on its own is also a measurable limit. The record cannot see
the difference between "the fifth machine's subnet changed" and "position five is now a different
subregion," because all it has is an address and attributes. Both look like the same thing: an
address, changed attributes. Only the person who wrote the record knows the difference, and even
that person might mistake the five items for a real change while reading the plan. The count-based
form's risk is not hidden — it is written plainly in the plan — but telling apart the plan's six
items from reality's one item is left to the reader.

## The Shifted Address's Counterpart on the Measurement Network

```js
// cost.mjs — counts a plan item in the measurement network's own unit: missed meter-reading batches. Model.
import { SUBREGION, FORM, plan } from "./iteration.mjs";
const OUTAGE = 105, PERIOD = 15, RECREATE_S = 110, DELETE_S = 10;   // DP36, previous lesson's durations
const next = SUBREGION.filter((b) => b !== "east_2");

console.log("east_2 closed — same real change, three record forms");
console.log(`${"form".padEnd(21)}${"recreate".padStart(9)}${"out-of-service machine-s".padStart(27)}${"missed batch".padStart(15)}${"apply s".padStart(10)}`);
for (const [name, f] of FORM) {
  const p = plan(f(SUBREGION), f(next));
  const s = p.recreate * OUTAGE;
  console.log(`${name.padEnd(21)}${String(p.recreate).padStart(9)}${String(s).padStart(27)}` +
    `${String(p.recreate * Math.floor(OUTAGE / PERIOD)).padStart(15)}` +
    `${String(p.recreate * RECREATE_S + p.delete * DELETE_S).padStart(10)}`);
}
console.log("\nthe closing subregion's own batches were never going to be collected anyway;");
console.log("the loss above belongs to the nine sound subregions.");
```

```
east_2 closed — same real change, three record forms
form                  recreate   out-of-service machine-s   missed batch   apply s
manual, ten blocks           0                          0              0        10
count-based                  5                        525             35       560
set-based                    0                          0              0        10

the closing subregion's own batches were never going to be collected anyway;
the loss above belongs to the nine sound subregions.
```

When the numbers are read in the measurement network's unit, the decision becomes clear. The
closing subregion's machine was going away regardless; the loss above belongs to the nine
subregions standing sound. In the count-based form, five sound machines each go out of service for
105 seconds: 525 machine-seconds and **35 missed meter-reading batches**. In the manual and
set-based forms, these items are zero. Apply time is 560 seconds against 10.

A missed batch does not come back. The meters keep reading, but because nobody pulls during that
window, a 105-second gap opens in those five subregions of the measurement network — and there is
no trace of that gap anywhere in the record. The record is correct, the plan is correct, apply is
correct; the only thing lost is **data**.

This section should close by naming an item the record does not cover: the subregion list itself.
Ten subregion names are written in the record, but which subregion has meters is not something the
record knows — it was hand-copied from a list the field team keeps. When a subregion closes, the
record does not learn this on its own; someone goes and deletes it from the list. The trigger for
this change — the record's most expensive item — sits entirely outside the record.

## The Record Form Decision

| Form | Lines (10 resources) | Items on removal from the middle | Shifted address | Exception branch | When |
|---|---|---|---|---|---|
| Manual, ten blocks | 50 | 1 | 0 | 0 | Resources genuinely differ from one another |
| Count-based | 7 | 6 | 5 | 1 per exception | Order is meaningless, a set that only grows from the end |
| Set-based | 18 | 1 | 0 | 0 | A set whose elements have identity |

The table is summarized by a single measure: **the price of saving lines is address stability.**
The count-based form brings the manual form's 50 lines down to 7 — sevenfold — and in return
produces five to six needless recreations on every change in the middle. The set-based form comes
down to a third of the manual form at 18 lines and never loses address stability; the price it pays
is that subregion names get written a second time in the record, as map keys.

The decision is not between three forms, it is between two questions. First: are these ten resources
copies of one another, or does each have its own identity? On the measurement network, the second
is true — `east_2` is not an index, it is a subregion standing in the field. Second: does this set
change from the middle? The subregion list changes through closings and openings, so it does. The
two answers together point to the set-based form; the count-based form is not cheap enough here to
make its 7 lines worth choosing over 18.

## Summary

- All three record forms produce the same 10 real resources with the same attributes; the
  difference is not in reality, it is in the address the record gives a resource.
- Lines: manual 50, set-based 18, count-based 7. When the exception count rises to 3, the
  count-based form piles up 3 nested conditions in a single expression, while the branch count
  stays 0 in the other two.
- When east_2 closed, the manual and set-based forms produced 1 item, the count-based form
  produced 6; 5 addresses shifted to a different real resource. Inserting a subregion in the
  middle came to 1 versus 7 and 6.
- Appending at the end came to 1 item in all three forms: count-based iteration is the right form
  for sets whose order carries no meaning and that only grow from the end.
- The plan was not wrong — it said "5 will be recreated" and 5 were recreated, a ratio of 5/5. The
  5-item gap between expectation and plan was born from the record's form.
- In the measurement network's unit: 5 sound machines out of service for 105 seconds each, 525
  machine-seconds, 35 missed meter-reading batches, a 560-second apply — in the set-based form,
  all of that is 0 and 10 seconds.

## Next Step

This lesson counted plan items but always took the plan as a given: the items came from somewhere.
The next lesson builds that somewhere — it writes the **planner** itself, the thing that compares
the record against state and produces an add, update, recreate, or delete decision item by item.
What gets measured is the plan's own accuracy: how many items are not in the plan but happen at
apply, how many items are in the plan but do not happen, and how many items marked "will be
recreated" are actually recreated.
