Fitness Functions: Telling If Your Architecture Still Works
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.
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 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.
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.
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."
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.
| Domain | Quality it guards | The fitness function |
|---|---|---|
| SaaS / web | Responsiveness | p95 latency stays under a budget in a load test |
| Fintech | Auditability | every ledger write has a matching audit record |
| E-commerce | Page weight | JS bundle and Speed Index under a CI budget |
| Healthcare | Privacy | patient data never imported outside the PHI boundary |
| Embedded / automotive | Resource limits | firmware fits the chip's flash and RAM |
| Machine learning | Model quality | accuracy floor, latency ceiling, bounded data drift |
| Games | Frame budget | p99 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.
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.