---
title: 'Engineering Practices'
source: 'https://academia.sh/en/courses/process-and-team/engineering-practices'
course: 'Process, Team and Delivery'
language: en
updated: '2026-08-23T07:01:07+00:00'
license: 'CC BY-SA 4.0'
---

# Engineering Practices

Measuring feedback delay: how many steps and how many hours separate the step a defect is born in from the step it is found in, and how this delay, rework, and total work effort — including pair programming's two-person cost — change as integration frequency is scanned from two hours to forty hours and pairing ratio from zero to one.

The previous four lessons always took one number as an assumption: when a defect is found, there is a
return trip. How many items came back and what they cost was counted, but **when the defect is found**
was read off the shape of the process every single time. As this lesson closes the topic, it measures
that number directly: **feedback delay**, the distance between the step a defect is born in and the
step it is found in.

The distance is measured in two separate units, and they do not say the same thing. Delay in **steps**
gives how many stages later a defect appears; delay in **hours** gives how long those stages took. Two
practices touch these two units separately: integration frequency shortens the hours, pair programming
shortens the steps. Because the delivery pipeline's mechanics are run elsewhere in this curriculum, here the
pipeline is nothing more than a period. The through-line is the regional library network, and it is
fiction.

## Where the Defect Is Found

The block below is a **model**, not a measurement.

**PM25 — forty-eight work items are derived from a generator; the generator is self-written and the
seed is visible.** Every item has a size (3–12 hours) and a module it touches; the same set is used at
every setting. Eight developers work.

**PM26 — the earliest step a defect can be seen in is given as input, as a class.** The path is five
steps: writing, local, integration, release, operations. A **writing**-class defect is caught while
it is being written if a second pair of eyes is present; otherwise it stays hidden until integration.
An **integration**-class defect surfaces only once it is merged with someone else's work, a
**release**-class defect at release testing, and an **operations**-class defect only in operations.
Release testing runs every forty hours, and the operations window opens every eighty hours.

**PM27 — in a paired item, a writing-class defect is fixed on the spot** and never becomes a separate
work item. This is pair programming's only gain in the model.

**PM28 — every integration event costs a fixed 0.5 hours**, plus 0.06 hours of overlap cost for every
pair of items integrated in the same batch. This is where the price of infrequent integration comes
from: as the batch grows, the pair count grows quadratically.

Rework is one-third of the item plus 1 hour for every item completed **in the same module** between
the defect's birth and the moment it is found.

```js
// practice.mjs — feedback delay (model); integration frequency and pairing ratio are scanned
import { writeFileSync } from "node:fs";

const DEVS = 8;             // developer count
const RELEASE = 40;         // release testing runs on this cadence (hours)
const OPERATIONS = 80;      // operations window
const FIXED = 0.5;          // fixed cost of one integration event (hours)
const OVERLAP = 0.06;       // overlap cost for every pair of items integrated in the same batch (hours)
const STEP = ["writing", "local", "integration", "release", "operations"];

// PM25: the generator is self-written, the seed is visible; the same item set is used at every setting.
let seed = 20260127;
const rand = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648;
const MODULE = ["loan", "fee", "membership", "catalog", "branch", "identity"];
const ITEM = [...Array(48)].map((no) => {
  const size = 3 + Math.floor(rand() * 10);              // 3-12 hours
  const m = MODULE[Math.floor(rand() * MODULE.length)];
  const r = rand();
  // PM26: the earliest step a defect can be seen in is a class, given as input
  const kind = r < 0.55 ? null : r < 0.76 ? "writing" : r < 0.90 ? "integration" : r < 0.96 ? "release" : "operations";
  return { no, size, module: m, kind };
});

const next = (t, p) => Math.ceil((t + 1e-9) / p) * p;   // the next periodic occurrence

function run(T, ratio) {
  const free = Array(DEVS).fill(0);
  const item = ITEM.map((k, i) => ({ ...k, paired: i % 4 < Math.round(ratio * 4) }));
  let effort = 0;
  for (const k of item) {
    const count = k.paired ? 2 : 1;
    const chosen = free.map((t, i) => [t, i]).sort((a, b) => a[0] - b[0]).slice(0, count);
    const start = Math.max(...chosen.map(([t]) => t));
    for (const [, i] of chosen) free[i] = start + k.size;
    k.end = start + k.size;
    effort += k.size * count;
  }
  // PM27: in a paired item, a writing-class defect is caught on the spot; others wait for their period
  const findings = [];
  for (const k of item) {
    if (!k.kind) continue;
    const [step, at] =
      k.kind === "writing" && k.paired ? [0, k.end]
      : k.kind === "release" ? [3, next(k.end, RELEASE)]
      : k.kind === "operations" ? [4, next(k.end, OPERATIONS)]
      : [2, next(k.end, T)];
    const builtOn = item.filter((x) => x.module === k.module && x.end > k.end && x.end <= at).length;
    // a defect caught while writing is fixed on the spot; it never becomes a separate work item
    findings.push({ no: k.no, kind: k.kind, step, delayHour: at - k.end,
                 rework: step === 0 ? 0 : Math.ceil(k.size / 3) + builtOn });
  }
  const rework = findings.reduce((a, b) => a + b.rework, 0);
  // PM28: every integration event pays a fixed cost, plus overlap for every pair of items in the batch
  const last = Math.max(...item.map((k) => k.end));
  let events = 0, overlapCost = 0;
  for (let t = T; t <= next(last, T); t += T) {
    const batch = item.filter((k) => k.end > t - T && k.end <= t).length;
    if (batch === 0) continue;
    events += 1; overlapCost += (OVERLAP * batch * (batch - 1)) / 2;
  }
  const integrationCost = +(events * FIXED + overlapCost).toFixed(1);
  const avg = (f) => findings.reduce((a, b) => a + f(b), 0) / findings.length;
  return { T, ratio, duration: last, findings, findingCount: findings.length, events, overlapCost: +overlapCost.toFixed(1),
    delayStep: avg((b) => b.step), delayHour: avg((b) => b.delayHour),
    pairFound: findings.filter((b) => b.step === 0).length,
    rework, integrationCost, effort,
    total: +(effort + rework + integrationCost).toFixed(1) };
}

const strip = ({ findings, ...r }) => r;
const FREQUENCY = [2, 4, 8, 16, 40].map((T) => run(T, 0));
const RATIO = [0, 0.25, 0.5, 0.75, 1].map((o) => run(4, o));
writeFileSync("practice.json", JSON.stringify({ FREQUENCY: FREQUENCY.map(strip), RATIO: RATIO.map(strip) }));

console.log(`work items: ${ITEM.length}, base work: ${ITEM.reduce((a, k) => a + k.size, 0)} hours, ` +
  `developers: ${DEVS} (seed ${20260127})`);
console.log(`defect class: ${["writing", "integration", "release", "operations"]
  .map((s) => `${s} ${ITEM.filter((k) => k.kind === s).length}`).join(", ")}; ` +
  `defect-free ${ITEM.filter((k) => !k.kind).length}`);
console.log(`steps: ${STEP.map((a, i) => `${i} ${a}`).join(", ")}`);

const base = FREQUENCY[1];
console.log(`\nintegration every 4 hours, no pairing: where the defect is found`);
console.log(`${"defect class".padEnd(14)}${"count".padStart(6)}${"found at step".padStart(16)}` +
  `${"delay (steps)".padStart(16)}${"delay (hours)".padStart(16)}${"rework".padStart(12)}`);
for (const s of ["writing", "integration", "release", "operations"]) {
  const g = base.findings.filter((b) => b.kind === s);
  const o = (f) => (g.reduce((a, b) => a + f(b), 0) / g.length).toFixed(1);
  console.log(`${s.padEnd(14)}${String(g.length).padStart(6)}${STEP[g[0].step].padStart(16)}` +
    `${o((b) => b.step).padStart(16)}${o((b) => b.delayHour).padStart(16)}` +
    `${String(g.reduce((a, b) => a + b.rework, 0)).padStart(12)}`);
}
```

```
work items: 48, base work: 354 hours, developers: 8 (seed 20260127)
defect class: writing 14, integration 5, release 2, operations 2; defect-free 25
steps: 0 writing, 1 local, 2 integration, 3 release, 4 operations

integration every 4 hours, no pairing: where the defect is found
defect class   count   found at step   delay (steps)   delay (hours)      rework
writing           14     integration             2.0             2.1          45
integration        5     integration             2.0             3.4          16
release            2         release             3.0            20.5          15
operations         2      operations             4.0            56.5          13
```

The table shows why feedback delay has to be measured in two units. The fourteen writing-class
defects, with no pairing, are found **two steps** later, an average of 2.1 hours later; they wait for
integration when they could have been fixed on the spot. At the other end there are only two
operations defects, but their delay is 56.5 hours and they produce thirteen hours of rework — 6.5
hours per defect, twice the writing class. A small number of late-found defects is more expensive than
a large number of early-found ones.

## Scanning the Two Settings

```js
// delay-scan.mjs — reads the results practice.mjs wrote; two settings are scanned separately
import { readFileSync } from "node:fs";

const { FREQUENCY, RATIO } = JSON.parse(readFileSync("practice.json", "utf8"));
const FIXED = 0.5;
const table = (header, row, caption) => {
  const write = (h) => console.log(h.map((c, i) => String(c).padStart(header[i][1])).join(""));
  if (caption) { console.log(); console.log(caption); }
  write(header.map((b) => b[0])); row.forEach(write);
};

table([["integration", 13], ["events", 8], ["delay (steps)", 16], ["delay (hours)", 16],
       ["rework", 12], ["integration cost", 19], ["total", 9]],
  FREQUENCY.map((r) => [`${r.T} h`, r.events, r.delayStep.toFixed(2), r.delayHour.toFixed(1),
                     r.rework, r.integrationCost, r.total]),
  "integration frequency (no pairing):");

// PM29: the only cost of a shorter cadence is the fixed per-event cost; the break-even point follows.
const a = FREQUENCY[0], b = FREQUENCY[1];
const breakEven = (b.rework + b.overlapCost - a.rework - a.overlapCost) / (a.events - b.events);
console.log(`\nthe two-hour rhythm produces ${a.events} events, the four-hour rhythm ${b.events}; ` +
  `at a fixed cost of ${FIXED} hours per event, the two-hour rhythm comes out ${(b.total - a.total).toFixed(1)} hours ahead.`);
console.log(`this lead disappears once the fixed cost per event passes ${breakEven.toFixed(2)} hours.`);

table([["pairing ratio", 15], ["duration (hours)", 19], ["caught while writing", 23], ["delay (steps)", 16],
       ["delay (hours)", 16], ["rework", 12], ["effort", 9], ["total", 9]],
  RATIO.map((r) => [`${(100 * r.ratio).toFixed(0)}%`, r.duration, `${r.pairFound}/${r.findingCount}`,
                   r.delayStep.toFixed(2), r.delayHour.toFixed(1), r.rework, r.effort, r.total]),
  "pairing ratio (integration every 4 hours):");

// PM30: if pairing's two-person cost exceeds its gain, at what multiplier does it break even?
const y = RATIO[0], z = RATIO[RATIO.length - 1];
const multiplier = (z.effort - y.effort) / (y.rework - z.rework);
console.log(`\nat 100% pairing, delay falls from ${y.delayHour.toFixed(1)} hours to ` +
  `${z.delayHour.toFixed(1)} hours, and rework from ${y.rework} hours to ${z.rework} hours; ` +
  `in exchange, effort rises from ${y.effort} hours to ${z.effort} hours and the work stretches ${z.duration - y.duration} hours.`);
console.log(`rework per defect is ${(y.rework / y.findingCount).toFixed(1)} hours; ` +
  `for pairing to pay for itself, this number has to grow to ${multiplier.toFixed(1)} times that.`);
```

```
integration frequency (no pairing):
  integration  events   delay (steps)   delay (hours)      rework   integration cost    total
          2 h      21            2.26             8.0          82               13.3    449.3
          4 h      13            2.26             8.7          89               12.3    455.3
          8 h       7            2.26            10.0          94               13.6    461.6
         16 h       4            2.26            13.4         103               22.9    479.9
         40 h       2            2.26            21.4         132               53.6    539.6

the two-hour rhythm produces 21 events, the four-hour rhythm 13; at a fixed cost of 0.5 hours per event, the two-hour rhythm comes out 6.0 hours ahead.
this lead disappears once the fixed cost per event passes 1.25 hours.

pairing ratio (integration every 4 hours):
  pairing ratio   duration (hours)   caught while writing   delay (steps)   delay (hours)      rework   effort    total
             0%                 50                   0/23            2.26             8.7          89      354    455.3
            25%                 62                   4/23            1.91             7.8          69      443    523.8
            50%                 73                   7/23            1.65             6.3          59      530    601.9
            75%                 85                   9/23            1.48             5.3          50      615    677.5
           100%                 90                  14/23            1.04             4.7          35      708    755.8

at 100% pairing, delay falls from 8.7 hours to 4.7 hours, and rework from 89 hours to 35 hours; in exchange, effort rises from 354 hours to 708 hours and the work stretches 40 hours.
rework per defect is 3.9 hours; for pairing to pay for itself, this number has to grow to 6.6 times that.
```

## Two Practices, Two Separate Components

The first table's most striking column is the one that does not move. Even as integration goes from
every two hours to every forty, **delay in steps stays fixed at 2.26**: the defect is still found at
the same step, only reaching that step takes longer. Delay in hours rises from 8.0 to 21.4, and rework
from 82 to 132 hours. At the forty-hour rhythm, integration cost also jumps from 13.3 to 53.6 hours,
because as the number of items integrated in a single batch grows, the overlap-pair count grows
quadratically. **Integrating more often loses nothing anywhere in this model**; the two-hour rhythm is
6.0 hours ahead of the four-hour rhythm, and that lead only disappears once the fixed cost per event
passes 1.25 hours. That is the number that needs to be measured: how long one integration event takes.

The second table does exactly the opposite. Pairing touches **delay in steps** — it falls from 2.26 to
1.04, because fourteen out of fourteen writing defects are caught at step zero and produce no rework
at all. Rework falls from 89 to 35 hours. Its price, though, is on a scale seen nowhere else in the
settings: effort rises from 354 hours to **708 hours**, because the same work is now done by two
people, and the work stretches by forty hours, because eight people now run half as many items at the
same time.

The arithmetic is plain. 354 hours of extra effort buys 54 hours of rework savings. For pairing to pay
for itself in this model, rework per defect has to grow from 3.9 hours to **6.6 times** that. This
multiplier is not imaginary: operations-class defects already run at 6.5 hours per defect, and if a
defect slips into operations, rework climbs well above what this model shows. So pairing is an
expensive habit where defects are cheap, and a cheap insurance policy where defects are expensive — and
which one applies is a measurable question.

## Summary

- Feedback delay was measured in two units: delay in steps gives how many stages later a defect
  appears, delay in hours gives how long those stages took.
- At four-hour integration, 14 writing defects are found 2 steps and 2.1 hours later; 2 operations
  defects are found 4 steps and 56.5 hours later and produce the most expensive rework at 6.5 hours
  per defect.
- Integration frequency never changes the step count (fixed at 2.26), but it changes the hours: going
  from 2 hours to 40 hours, delay rises from 8.0 to 21.4 hours, rework from 82 to 132 hours,
  integration cost from 13.3 to 53.6 hours.
- The only price of integrating more often is the fixed cost per event; the two-hour rhythm's lead
  only disappears once that cost passes 1.25 hours.
- Pairing lowers delay in steps from 2.26 to 1.04 and rework from 89 to 35 hours, but raises effort
  from 354 to 708 hours and stretches the work by 40 hours; breaking even requires rework per defect to
  grow to 6.6 times its size.

## Next Step

Five lessons chose the shape of the process, limited flow, counted the price of coordination, and
shortened feedback delay. All five share one blind spot. Every one of these measurements took **who
hands off to whom** as a given: the step boundary in the lifecycle model, the backlog's owner in the
iteration, the team the WIP limit covers in the flow model, which team a module falls to in the scaled
arrangement. Every time, that boundary was an input, and it was never questioned. But that boundary
comes from somewhere: teams were split some way, modules were divided up some way, and every boundary
a work item crosses is really a **team boundary**. The next topic asks that question — how much does
the way teams are split determine the architecture, and when the communication structure and the
module structure do not overlap, how much do the waiting, rework, and coordination rounds counted
across these five lessons grow.
