The combination no test ever ran
On August 1, 2012, Knight Capital pushed a deploy to eight servers. One of them didn't take the new code. That single mismatch — eight servers, new code or old, 256 possible configurations and exactly one of them ever tested — woke a decade-dead code path in production and lost the firm $440 million in 45 minutes.
256 possible configurations. They'd tested exactly one. $440 million, gone in 45 minutes.
No single line of that code was wrong. The bug was a state nobody designed, a combination no test ever ran — and you've shipped the small version of it. The spinner keeps spinning over content that's already on screen. The error toast slides in over a page that loaded fine. The empty state flashes for a frame before the data it already had paints.
Three fields behind it — isLoading, isError, and data that's either
there or not. Eight combinations between them; four mean something — idle,
loading, loaded, failed. The other four are the bug: the spinner still turning
while the data sits right there, loading and errored at once, the render
that's somehow none of them. Your code has to survive all eight, because the
type says all eight exist.
That shape has a name: it's useQuery's old return type — and the footgun was
common enough that React Query itself replaced those booleans with a single
status field (pending | error | success).
State explosion
useQuery · 3 fetch booleans
Those three fields are tangled — most of the eight states are nonsense you can delete outright. Feature flags are the opposite: independent by design, so every combination is a real timeline your code runs in production. Open your dashboard, count the flags older than six months you "meant to clean up." I'll wait.
Now multiply them. Each flag forks the timeline — on or off. One flag, two timelines. Two, four. Three, eight. It doubles every single time.
Find your team's number — 20flags1,048,576timelines — and every one of those is a path your code can take in production.
Nobody tests a million timelines. QA signs off on the dozen you flip on purpose; the suite goes green. The rest just exist — in production, never enumerated by anyone, waiting for the right user to load the right page in the right order. That's Knight Capital's 256 configurations, one layer up — the same trap, now with your feature flags instead of eight servers.
And it rarely looks like a bug in the code — no crash, no stack trace, no failing test. It passed everything you wrote; it broke the one combination you didn't. It looks like "customer X is seeing the wrong price." You chase it as a race condition until someone pulls the flag history and sees this exact three-flag combo has never happened in the system's life, never will again after Friday's release, and is breaking precisely one invoice right now. Nobody designed that state. Every flag did what it said on the toggle. The combination was a timeline no human ever imagined.
Nobody told me this in school. That bug you couldn't reproduce usually isn't a logic error — it's a state you forgot existed, a branch of the timeline nobody meant to grow.
And it isn't just flags. One invisible thing explodes the state space in a way
nothing else does — and you've spent more of your career than you'd like
debugging what it causes: the NullPointerException, the undefined is not a function, the one check you forgot on the one path that mattered.
It's null. Its own inventor, Tony Hoare, calls it his
billion-dollar mistake —
he slipped it into a type system in 1965 "simply because it was so easy to
implement," and spent the next fifty years watching it cause "innumerable
errors, vulnerabilities, and system crashes."
Here's why it's the worst offender. A boolean you add on purpose, and you can
see it coming. null the language adds for you — quietly, to almost every
field at once, turning each T into T | null. It's a flag stapled to every
value you have: it doubles that field's states, and multiplies across all of
them. A form with 15 optional fields is 32,768 presence-and-absence states
before you've written a line of logic. (Your largest form — you know the one.)
A user model with isAdmin, isVerified,
isBanned, isDeleted has 16 states, about four of which make sense — and
at least one database row somewhere has all four set to true.
Out-of-order webhooks, a retry firing through a half-finished write, a message
that arrives twice or never — same shape every time. The set of states
your code can be in dwarfs the set it actually handles.
And you can't test your way out — the math is against you. Knight was the cheap version; a 2015 study of production failures in major distributed systems found that about a quarter trace to configuration combinations nobody tested — the same trap at the infrastructure layer. The state space expands by default; your ability to verify it grows linearly at best. The gap widens every time someone ships a feature.
TL;DR — the two cheapest cuts. Most prod bugs aren't bad lines; they're states nobody enumerated. You can't test your way out — the combinations explode — but you can shrink the state space so the bad states can't exist:
- Discriminated union — a type that's always exactly one of a few shapes, never a mix; impossible states won't even compile.
- Database constraint — enforce what the type can't, where nothing can bypass it.
Start today: take one boolean-soup model and collapse it into a union. For covering arrays, model checking, and why the spec is now cheap to write — Part 2.
Now the AI is branching it too
An LLM writes a growing share of your code now, faster than review can keep up. Generation scales; careful reading doesn't. The branches multiply faster than anyone can check them by hand, so I've stopped betting on the check. You can make the check cheaper — tighten the feedback loop so mistakes surface fast, turn your conventions into lint rules the CI enforces — and you should. But the cheapest branch to review is the one that can't exist. So prune the branches that should never exist — make them impossible to write down. A branch the AI can't spell is a bug it can't ship.
Pruning means the bad branch can't be spelled
Almost everything a type allows is invalid. The legal states are a small island; what the type actually permits is an ocean. It took me embarrassingly long to see my own defensive code for what it was — the guard clauses, the "this shouldn't happen" comments, the same validation in five different places. All of it babysitting combinations that should never have been representable in the first place.
The fix is to shrink the ocean until it matches the island. Two essays taught me the moves, from different angles:
Make illegal states unrepresentable is Wlaschin's: design the shape so bad states have no spelling. If a state can't be constructed, it can't be passed in, returned, or stored. Parse, don't validate is King's: check incoming data once at the boundary, hand back a type with the invariant baked in, and the compiler carries it from there.
Both cut at the door. The toolkit runs deeper from there.

Kill the booleans
The classic example. Nobody designs this. Three boolean lifecycle fields
accrete one feature at a time: isDraft this quarter, isArchived two
quarters later, each added by someone who never looked at the other two (and
who has since moved to another team):
interface Order {
isDraft: boolean
isPublished: boolean
isArchived: boolean
}Three booleans. Eight possible states, and ✗ nonsense. The other five — published and draft at once, archived and draft and published — are nonsense your code still has to handle, because the type says they exist.
You don't have to swear off booleans. The test I use is
Matt Pocock's:
bad booleans store state; good booleans are derived from it. A stored
isPublished you set by hand is the disease; const isPublished = status === "published"
is fine: it's computed, so it can't contradict anything.
The fix is a discriminated union — a type that says "this value is exactly one of these shapes, never a mix." Every major language has one; what changes is how hard the compiler makes it to get wrong. (No Sorbet in your Rails project? The DB constraint section is the version that enforces the same rule on any stack.)
// TypeScript
type Order =
| { status: "draft"; content: string }
| { status: "published"; content: string; publishedAt: Date }
| { status: "archived"; content: string; archivedAt: Date }Eight states → three, and the nulls went with them. The boolean version
needed a nullable publishedAt: a real date on published orders, null on
drafts, and nothing stopping a draft from carrying a stray timestamp or a
published order from carrying none. The union deletes the field from every
variant that shouldn't have it, so publishedAt exists only on a Published
order. No nullable column to forget, no illegal combination, no billion-dollar
mistake left to check for. And "published and draft at once" never had a shape
to live in.
(No union handy? The one-level-down version is a language that makes null
opt-in — Rust's Option, Kotlin's ?, TypeScript's strictNullChecks — so
absence becomes a case the compiler forces you to name.)
TypeScript, Python, and Rust make this a compile-time hard stop. Go and C# enforce the structure but only refuse an illegal value at runtime; there's no exhaustiveness proof at compile time. That difference matters when the union grows:
It's the same cut for the spinner from the top: model the fetch as one of idle | loading | error | data and "loading and errored at once" has no shape to live in — not caught at runtime, just unbuildable.
And when you switch on this, the compiler makes you handle every case:
// TypeScript — miss a case and it won't compile
function render(order: Order) {
switch (order.status) {
case "draft": return renderDraft(order.content)
case "published": return renderPublished(order.content, order.publishedAt)
case "archived": return renderArchived(order.content, order.archivedAt)
}
}Add a fourth status and this stops compiling until you handle it. Rust enforces
the same exhaustiveness as a hard error; Python's assert_never and Sorbet's
T.absurd catch it at type-check time; Go and C# only warn or throw at
runtime (neither proves a class hierarchy exhaustive).
And the same idea works at the database level. SQL is weaker than a type system, but it's enforced on every write, by every client, forever:
▶The same rule as a Postgres table
-- PostgreSQL — the database refuses to store illegal states
CREATE TABLE orders (
id UUID PRIMARY KEY,
status TEXT NOT NULL CHECK (status IN ('draft', 'published', 'archived')),
content TEXT NOT NULL,
published_at TIMESTAMPTZ,
archived_at TIMESTAMPTZ,
-- Same invariant as the discriminated union, at the persistence layer.
-- Archived drops `published_at` because archive is reachable from draft
-- (never published) — so the union can't carry it on `archived` either; the
-- two layers agree. If archive only ever followed publish, keep it on both.
CHECK (
(status = 'draft' AND published_at IS NULL AND archived_at IS NULL) OR
(status = 'published' AND published_at IS NOT NULL AND archived_at IS NULL) OR
(status = 'archived' AND published_at IS NULL AND archived_at IS NOT NULL)
)
);Your TypeScript types stop at the network boundary. Your database doesn't, and neither does CHECK. Even if some service in another language forgets the rule, the constraint catches it. We'll come back to this.
Add a fourth status? The compiler points at every place that needs updating, and the code won't build until you've handled them all.
A union collapses one field's states. But some rules span two independent
fields — a voided invoice whose payment_status is somehow still
succeeded — and no single type can spell that combination away, because each
field is legal on its own. That needs a cut that sees both fields at once.
The cut no app can bypass
Every cut so far lives in your code, which means it stops the moment data crosses a boundary you don't own: the JSON the API just received, the row another service wrote, the message off Kafka. Your Order type guarantees nothing about any of them.
There's a word for what all these cuts enforce: an invariant, the one sentence your data must never contradict. "A deleted row never comes back." "A refunded order can't reopen." Bugs are violated invariants. The space the fence needs to protect can be astronomical; the fence itself stays small — a 2021 proof of Paxos searched billions of candidate invariants and found the rule that pins it fits in a handful of terms. The only question is where you enforce it, from weakest to strongest:

▶The full spectrum, as a table
| Mechanism | When checked | What happens on violation |
|---|---|---|
| Comments / docs | Never | Nothing |
| Runtime assert | At call site, sometimes | Crash, hopefully in dev |
| Tests | At CI time | Build fails (for cases you wrote) |
| Linters | At lint time | PR fails (for patterns you encoded) |
| TypeScript types | At compile time, erased at runtime | Build fails, but can be bypassed with as any and stops at the network boundary |
| Strong types (Rust, Haskell) | At compile time, harder to bypass | Build fails earlier and unsafe is opt-in rather than the escape hatch |
| Schema validation (Zod, Pydantic) — a library that checks incoming data against a declared shape | At system boundary, at runtime | Reject input; on success, the type carries the invariant forward |
| Database constraints | At every write, by every client, forever | INSERT/UPDATE rejected — the only layer no application can bypass |
Most teams underuse the database here. I did too, for years, and it's probably the strongest invariant layer you have.

Each constraint deletes a whole class of bad state. And unlike your types, they hold on every write, from every client, forever, no matter which service forgot:
NOT NULLeliminates a state.UNIQUEeliminates a class of duplicate states.FOREIGN KEYeliminates orphan-reference states.CHECK (status IN ('active', 'done', 'archived'))collapses an unbounded text field to three legal values.
▶Why CHECK and not a native ENUM?
Evolving the set stays an ordinary migration. A Postgres ENUM only lets you
ADD VALUE; dropping or reordering one means recreating the whole type and
every column built on it. text + CHECK is the trade most teams at scale
make: GitLab's schema alone carries 35 enum-whitelist checks doing exactly an
ENUM's job.
And the teams at serious scale already live this way:
GitLab's schema declares 2,419
CHECKconstraints — 82 of them exclusive-arc checks ("exactly one of these columns is set," the database half of a discriminated union).
It's institutionalized, too: a first-class migration helper,
add_multi_column_not_null_constraint, makes pushing "exactly one of these is
set" into the schema a one-liner any engineer reaches for. That's what a
codebase looks like once it takes the database seriously as an enforcement
layer.
The part that got under my skin once I started counting: your model already has a spec for which combinations are legal. It's just split across two layers that don't talk to each other.
One side is the pile of conditional validations every non-trivial model
accumulates (validates … if:, validate, conditional callbacks). GitLab's
app/models carries 173 of them, Mastodon 72. That's literally a decision
table — the predicates are the parameters, the rules fire on combinations
nobody enumerates. But those validations drift: they're code that has to be
remembered, and update_column / upsert_all / insert_all skip them
entirely.
The other side is the CHECKs and partial indexes above, which can't drift,
because the constraint is the enforcement. So the legal state space ends up
half-declared in a layer that rots and half-declared in a layer that can't. The
bugs live in the gap between them.
▶Two more constraints worth knowing: partial unique indexes and EXCLUDE
A partial unique index is the idiomatic way to enforce state-machine cardinality at the persistence layer:
CREATE UNIQUE INDEX one_active_subscription_per_customer
ON subscriptions (customer_id)
WHERE status = 'active';After this, the database itself refuses to ever store two simultaneously
active subscriptions for the same customer. No race condition, no
application bug, no service in another language can produce that state. This
isn't a clever trick either: GitLab's schema carries 163 of these
state-gated partial unique indexes (WHERE status = ...), enforcing
"at most one row in this state" across the codebase.
An EXCLUDE constraint generalizes the same idea to ranges and
overlaps. Imagine room bookings:
-- the `=` operator class needs the btree_gist extension:
CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE bookings ADD CONSTRAINT no_overlap
EXCLUDE USING gist (
room_id WITH =,
during WITH &&
);Every booking system reimplements "no double-booked rooms" in application code, badly, with race conditions the database just refused to permit.
And if you want to know what a missing constraint costs, the cleanest case is Robinhood, 2020–21. The app showed customers false negative balances and fired 84,100 erroneous margin calls off them, drawing FINRA's record $70 million penalty for the "significant harm" it caused. Knight was the combinatorial kind — 256 configs, one tested. Robinhood is the missing-invariant kind: "never show a user a balance the ledger doesn't support" is a rule, and no layer of the stack enforced it.
That's the cut a type can't make: it held even when every layer of application code — the validation, the guard, the recompute — was wrong. Schema validation at the boundary helps; a database constraint helps more, because it doesn't trust the application to be right.
So I've stopped trying to pick the one right mechanism. Stack them. Each layer in that table cuts the state space a little; together they pin the system down to something close to "only valid states are reachable."
The cut at the door

It's what Alexis King named parse, don't validate: check once at the boundary and return a type with the invariant built in, not a boolean everything downstream has to re-check:
▶Parsing vs. validating, in code
// Validation: check, hope, repeat everywhere
function isValidOrder(input: unknown): boolean {
/* ... */
}
function processOrder(input: unknown) {
if (!isValidOrder(input)) throw new Error("bad")
// input is still `unknown`. You'll check again. And again.
}
// Parsing: check once, type carries it forever
const result = OrderSchema.safeParse(rawJson)
if (result.success) {
const order: Order = result.data // ← invariant now lives in the type
processOrder(order) // No checks downstream. Compiler enforces.
}Zod, Pydantic, io-ts, Valibot — same trick: a runtime invariant goes in, a type-level invariant comes out. Pay the cost once, at the door; the compiler enforces it for the rest of the program's life.
Start cutting
Two cuts. Make one today.
Today. Find one model with three or more status booleans, or a status
string with no CHECK behind it. Replace the booleans with a discriminated
union, or add the constraint. One bad state that can no longer be spelled.
It's the cheapest cut there is, and you'll feel it.
This week. Take the cross-field invariant your app assumes but never
enforces — "a voided invoice is never paid," "an active subscription has a
customer" — and push it into the database: a CHECK, a partial unique index,
an EXCLUDE. The cut no service can route around.
These two cuts delete most of the bad states outright. For the combinations that survive — the space too large to type away, the sequence nobody can enumerate, the 3am bug that passed every test — that's the verification layer: Part 2 — covering arrays, model checking, and the bug that survives every test.
Prune the timeline. One cut at a time.
References
▶Sources & further reading
- Alexis King, Parse, Don't Validate (2019)
- Yaron Minsky, Effective ML (Jane Street, Effective ML talks at CUFP mid-2000s and the "Effective ML Revisited" blog post) — the OCaml-community version of "make illegal states unrepresentable," predating the F# write-up by a decade
- Scott Wlaschin, Making Illegal States Unrepresentable (F# for Fun and Profit) — the popular F#/TypeScript-era restatement
- Chris Krycho, Making Illegal States Unrepresentable in TypeScript
- David Harel, Statecharts: A Visual Formalism for Complex Systems (Science of Computer Programming, 1987) — the source XState's hierarchical and parallel states descend from
- Tony Hoare, Null References: The Billion Dollar Mistake (QCon London 2009) — the inventor of the null reference on why every nullable field is a state you didn't mean to add
- Doug Seven, Knight Capital — A DevOps Cautionary Tale
- SEC, In the Matter of Knight Capital Americas LLC (Order 34-70694, 2013) — primary source for the $440M trading loss and the $12M penalty
- CNBC, Robinhood to pay $70 million for outages and misleading customers (2021) — the FINRA order behind the missing-invariant example
- Tianyin Xu et al., Hey, You Have Given Me Too Many Knobs! (FSE 2015) — misconfiguration as a leading cause of production outages
- Ben Moseley, Peter Marks, Out of the Tar Pit (2006) — the canonical argument that state is the primary source of complexity; "for every single bit of state that we add, we double the total number of possible states"