I'd seen "fitness function" mentioned for a while without really knowing what it meant, so I read the book that introduced the term. It turned out to be a more modest idea than the name implies, and a useful one. Here is what I took away from it.

commit CI build fitness function production rejected → back to the author
A fitness function sits at the gate of the pipeline. It scores one architectural quality and lets the change through only if the score still holds.

Where the idea comes from

One thing I hadn't realised: the phrase doesn't come from software at all. It's borrowed from evolutionary computing. When you solve a problem with a genetic algorithm, you breed many candidate solutions, and at each generation you need a number for how good each one is. That number is the fitness, and the function that computes it is the fitness function. To optimise an aircraft wing, the fitness function scores drag, weight, and lift. The algorithm keeps the fittest candidates, mutates them, and repeats. The fitness function is what defines "better."

In 2017 Neal Ford, Rebecca Parsons, and Patrick Kua pointed that idea at software in Building Evolutionary Architectures. Their framing is that your architecture keeps evolving whether or not you planned for it. So you give it a fitness function and steer deliberately. The definition they settle on is intentionally broad. A fitness function is "any mechanism that provides an objective integrity assessment of some architectural characteristic(s)." A unit test, a metric, a monitor, a security scan, a script that greps your dependency graph. If it objectively scores a quality you care about, it counts.

Why they exist

The problem they address is a familiar one. Ask a team "is the architecture still good?" and you get opinions. Those opinions tend to lag reality by months, because the only time anyone looks at the whole shape of the system is at a review. Each individual commit looks fine on its own. Nobody opens a pull request titled "make the system 8 ms slower and add one forbidden dependency."

A fitness function asks one question on every change: is this architecture still fit for its purpose? The moment the answer flips from yes to no, the build fails.

It takes a fuzzy quality like "fast enough" or "doesn't leak patient data", turns it into a test with a threshold, and runs it automatically on every push.

A latency fitness function, as a tripwire. The line is p95 response time as commits land. Ship a few risky commits and watch it cross the budget and fail the build. Drag the dashed line to move the budget.

A simple mental model

Underneath the vocabulary, every fitness function is the same four-step loop. Something changes. You measure one characteristic and compare it to a threshold you chose earlier. The result either lets the change through or stops it.

a change lands measure one quality below the threshold? pass merge / deploy fail break the build yes no
The loop behind every fitness function. The threshold is the one number you pick up front; everything else is plumbing.

They don't all take the same form, though. The book sorts them along a few axes, which I found useful for placing any given check.

scope atomic one quality, in isolation holistic several qualities together layering rule load test under security limits cadence triggered runs on a push or release continuous runs against production, always CI test live latency monitor result static dynamic fixed cap: < 300 ms budget that shifts with load
A single check usually sits somewhere on all three axes at once: an ArchUnit layering rule is atomic, triggered, and static; a production latency SLO is closer to holistic, continuous, and dynamic.

What they look like across industries

The easiest one to picture is structural: a rule about which parts of the code are allowed to talk to which. It's also the first thing to slip the moment someone reaches across a boundary "just this once."

UI layer service layer data layer UI → data forbidden each layer only calls the one below
A structural fitness function encodes the layering rule as a test, so the forbidden arrow fails the build instead of quietly becoming permanent.

In Java that rule is a few lines with ArchUnit, and it runs as an ordinary unit test:

@ArchTest
static final ArchRule ui_must_not_touch_persistence =
    noClasses().that().resideInAPackage("..ui..")
      .should().dependOnClassesThat().resideInAPackage("..persistence..");

The same shape shows up everywhere once you start looking: measure a quality, compare it to a threshold, fail the build. What changes from one industry to the next is which quality is worth guarding.

DomainQuality it guardsThe fitness function
SaaS / webResponsivenessp95 latency stays under a budget in a load test
FintechAuditabilityevery ledger write has a matching audit record
E-commercePage weightJS bundle and Speed Index under a CI budget
HealthcarePrivacypatient data never imported outside the PHI boundary
Embedded / automotiveResource limitsfirmware fits the chip's flash and RAM
Machine learningModel qualityaccuracy floor, latency ceiling, bounded data drift
GamesFrame budgetp99 frame time stays under 16.6 ms for 60 FPS

A few of those, in the form they actually take in a pipeline. A web performance budget, set in Lighthouse CI so it fails the build instead of just warning:

// lighthouserc.json
{ "assert": { "assertions": {
  "resource-summary:script:size": ["error", { "maxNumericValue": 170000 }],
  "speed-index":                  ["error", { "maxNumericValue": 3400 }]
}}}

An embedded build that refuses to ship if the firmware won't fit the part:

$ arm-none-eabi-size build/firmware.elf | awk 'NR==2 {
    if ($1+$2 > 262144) { print "FAIL: flash over 256 KB"; exit 1 }
    if ($2+$3 >  65536) { print "FAIL: RAM over 64 KB";    exit 1 }
    print "OK: fits the part" }'

And a machine-learning pipeline that won't promote a model that has regressed, slowed down, or started seeing different data than it was trained on:

assert metrics["auc"]            >= 0.82, "model regressed below the AUC floor"
assert metrics["p99_infer_ms"]   <= 50,   "inference too slow to serve"
assert psi(reference, live)      <  0.2,  "input distribution has drifted"

None of them are elaborate: a measurement, a threshold, and a check that blocks the merge when it isn't met. It's the same loop in each case; only the measured quality changes.

Pop quiz

A few scenarios to check the idea. The score doesn't matter much. The point is that the same definition keeps fitting regardless of how the scenario is worded.

Tap an answer. Right or wrong, you get the reasoning, which is the part worth keeping.

References

  • Neal Ford, Rebecca Parsons, Patrick Kua, Building Evolutionary Architectures (O'Reilly, 2017; 2nd ed. 2022) — the book that introduced architectural fitness functions.
  • "Fitness Functions", chapter 2 — the source of the definition and the atomic / holistic / triggered / continuous / static / dynamic taxonomy.
  • Thoughtworks Technology Radar, "Architectural fitness function" — the technique in short.
  • Fitness function in evolutionary computation — where the name was borrowed from.
  • ArchUnit — library for writing structural fitness functions as unit tests.
  • Lighthouse CI — performance budgets that fail the build.