5/1/2026 · 7 min read
Ockham's razor and code: against premature abstraction
Every layer you add is a claim about the future. Ockham asked us not to multiply entities; Epicurus explained why a concept with no experience behind it says nothing.
- #philosophy
- #architecture
- #simplicity
- #decisions

A PaymentProvider interface with a single implementation for two years. It was created in the first month, when no gateway had been integrated yet, because obviously one day there would be several.
One day the second one arrived. It didn’t fit. The first charged and returned the result in the same call; the new one confirmed by webhook half an hour later. The interface assumed a synchronous operation with an immediate answer, and that assumption was baked into the shape of the methods themselves. The whole thing had to be redone.
The result: two years paying for an indirection that protected nothing, plus the refactor the indirection existed to avoid. The abstraction wasn’t wrong about whether there would be several gateways. It got that right. It was wrong about how they would differ, which was the only part that mattered.
The principle, as stated
In the fourteenth century William of Ockham formulated a rule that outlived seven centuries: do not multiply entities beyond necessity. It was metaphysics, not engineering, and it doesn’t say “simple is pretty”. It says something stronger: between two explanations that account for the same facts, the one introducing fewer entities is preferable. Not out of aesthetic taste, but because every extra entity is one more claim that can be wrong.
In code, every layer is an entity. Every interface, every factory, every layer of indirection is a claim about the future: “this is going to vary in these ways”. And like every claim about the future, it’s almost always false in the details, which is where the bill lands.
Abstraction is a bet, not a gift
There’s an idea going round that abstracting is always a virtue, that code with many layers is more professional. It’s the other way round. Abstracting is betting.
When you create an interface with a single implementation you are betting that one day there will be several and that they will differ along the seam you’ve drawn. The first part comes off reasonably often. The second almost never, because getting it right would require knowing the second implementation, and if you knew it you wouldn’t be abstracting: you’d be describing.
The right abstraction pays for itself. The premature one is paid for by whoever maintains it, every day, for a future that arrived in a different shape.
Epicurus and words with no outline
Epicurus called his theory of knowledge the Canonic. The word comes from the canon, the carpenter’s rule: the straightedge you check things against. That’s a good place to take the name of an epistemology from, and it says a fair amount about what kind of philosophy it was.
His starting rule is the one that matters here. He held that nothing can be investigated without a prior notion laid down by repeated experience: having seen many horses leaves an outline of “horse”, and that’s why you can ask whether the thing over there is a horse or a cow. Without that outline the question can’t even be formed. Hence his warning: we would not have named a thing if we had not first learnt its outline. Using a general word without that experience behind it isn’t speaking precisely: it’s emitting a noise shaped like a concept, and arguing about it never ends, because each person has a different outline in mind.
An abstraction is exactly that, a general name. PaymentProvider claimed to name what all payment gateways have in common, having seen one. It wasn’t a concept: it was an expectation with a concept’s name. And like every word with no outline behind it, it generated its share of arguments that couldn’t be closed, because each person was defending a different idea of what the interface “should” cover and nobody had cases to settle it with.
Epicurus also had a recommendation for phenomena where the evidence doesn’t reach far enough to choose between several possible explanations: don’t choose. Hold all the ones compatible with what’s been observed, and wait. It’s an odd piece of advice for a physicist and an excellent one for a software architect. When you genuinely don’t know how something is going to vary, the disciplined move isn’t picking one form of variation: it’s building none yet, which is the only way of not closing off the rest.
The rule of three, now with a reason
There’s an old heuristic in the trade: don’t abstract until the third repetition. The first time you write the code. The second you duplicate — yes, deliberately — and put up with the discomfort. Only on the third do you extract the abstraction.
It’s usually justified by statistical prudence, and it works better with Epicurus’ justification. Three cases are, roughly, the minimum experience that lays down an outline. With one you have a case. With two you have a case and a suspicion, and the suspicion is usually that they resemble each other in what’s accidental. With three you start seeing what actually repeats and what was circumstance of the first.
On the third you aren’t guessing the pattern: you’re reading it. And the difference between those two doesn’t show in the diff, it shows two years later.
Duplicating twice is cheaper than maintaining, for two years, an abstraction that guessed wrong. Premature duplication is deleted in five minutes; premature abstraction puts down roots, because by then there is code written against it.
How I tell a necessary layer from a premature one
- How many real implementations does it have today? An interface with one isn’t an abstraction: it’s an ornament with a reading cost.
- Has the variation already happened, or am I imagining it? Abstracting over observed variation is design. Abstracting over imagined variation is fortune-telling with good presentation.
- Could I write in one line what the cases have in common? If I can’t, or it comes out with an “and” in the middle, I don’t have a concept yet.
- Can I introduce it later without pain? If extracting it the day it’s needed takes half an hour, don’t build it today. Wait for the day.
- Does this layer isolate anything, or does it just move the problem one house along? Indirection that protects nothing only adds one more hop to the reading.
The cost you can’t see in the diff
What makes premature abstraction dangerous is that it doesn’t show up as a problem. A diff with more interfaces, more layers and more files looks like serious work. Nobody rejects a PR for being “too well structured”.
The cost arrives later, scattered and impossible to attribute: the person who takes twice as long to follow a simple flow, the bug hiding three layers down, the trivial change that has to be made in five places because the abstraction split up a decision that should have lived together.
And there’s a worse cost, the one we got with the gateway: the wrong abstraction doesn’t just get in the way, it teaches wrong. Everyone who arrived afterwards read in that interface that charging was a synchronous operation, because that’s what the interface said. They wrote their code believing it. The day the webhook showed up, what needed fixing wasn’t a class: it was everything that had come to rest on a false claim about the world.
Why it matters to me
Distrusting the entities you introduce to explain something is an old habit. Every new concept has to earn its place: if the same facts are explained without it, it’s surplus.
Code is the same. Elegance isn’t in how many layers you hold up, but in how many you managed not to need. A system is mature not when there’s nothing left to add, but when there’s nothing left to remove without breaking it.
Seven centuries on, the razor is still the best piece of architecture advice I know, and now I use it with the Epicurean tag attached: when in doubt between two designs, keep the one that introduces fewer things that can be wrong — and don’t name what you haven’t seen three times.
Keep reading
The river and the stone: what should flow and what should stay still
Heraclitus and Parmenides argued over whether reality changes or endures. Every application settles that argument each time it decides where a value lives.
The button I didn't build: practical wisdom for deciding what gets shipped
Aristotle separated knowing how to do something from knowing whether it should be done. The second one decides whether a feature earns its place.
The right moment: technical judgment and shipping speed
The Greeks had two words for time. Almost every argument about shipping fast uses the wrong one.
