← Back to list

Entropy Always Wins

There’s an unwritten law in software, more reliable than any design pattern and more universal than any team’s conventions: your codebase…

Stephen Begot in malt-engineering · 2026-06-22 11:31 · 0 claps · 9.2 min read
#entropy #malt #software-engineering #code-quality
Open on Medium ↗
Wiki topics: ⚖️ · Law & Justice

Entropy Always Wins

There’s an unwritten law in software, more reliable than any design pattern and more universal than any team’s conventions: your codebase will end up a mess.

Not because your colleagues are bad at their jobs. Not because you made the wrong calls along the way. It’s just what happens to complex systems. Entropy pulls in one direction in the physical world, and it does the same thing to code: toward disorder.

The good news is you’re not just along for the ride. The bad news is you can’t actually win. You can only slow things down.

That’s what this is about — why the mess is inevitable, the ways we speed it up without noticing, and how to play a smart losing game against it.

1. So what is entropy, exactly?

Picture the universe 13.8 billion years ago.

Every scrap of matter and energy that would later become billions of galaxies, crammed into a single point — infinitely dense, infinitely hot, and infinitely ordered. One state. One configuration. Entropy close to zero.

Then it blew apart. It expanded. And ever since, without pause, the universe has been thinning out, cooling down, spreading itself thin. Stars ignite and burn out. Matter drifts apart. Here and there things briefly get more complex — a planet, then life on it — but the overall trend never reverses. The whole thing is drifting toward its most likely state: uniform, cold, quiet.

That’s really all entropy is. A direction. The way any system drifts when you stop paying attention to it.

In its most general form, entropy counts the number of ways a system could be arranged. The more ways a state has of existing, the more likely it is — and the more entropy it carries.

You find the same idea across very different fields.

In statistical physics, Boltzmann tied the entropy of a gas to the number of microscopic arrangements consistent with what you actually measure. Heat the gas and you multiply the ways its molecules can be distributed. That’s entropy going up.

In information theory, Shannon used entropy to measure the uncertainty in a message. Something perfectly predictable — “aaaaaa…” — carries no entropy at all. Random noise carries the maximum. Information is whatever cuts the uncertainty down; noise is whatever adds to it.

And in cosmology, the whole history of the universe is one long walk toward maximum entropy — the “heat death,” a state so uniform that no energy gradient is left to do any work, and nothing happens anymore.

What all of these share is a single point: leave a system alone, with no energy spent to hold it together, and it slides toward disorder. Not because something malicious is pushing it there, but because the messy states massively outnumber the tidy ones.

There are a handful of ways to be neat. There are millions of ways to be a mess.

Software is no exception. A fresh codebase starts with low entropy — a few files, few dependencies, one obvious way to do things. Then time does its work. Features pile on, people come and go, bugs leave scar tissue, the business shifts underneath you. Abstractions start to leak. Conventions drift apart. The same concept ends up with three different names depending on which module you’re in. The tests still cover what mattered in 2019, not what’s true today.

There are endless ways to write tangled code and only a few ways to keep it clean. The odds always tilt the same way.

We have a friendlier name for software entropy: technical debt. But the word “debt” is misleading. It implies you chose to borrow. In practice a good chunk of it builds up even when you do everything right. Rigorous reviews, solid test coverage, a strong team — none of it stops the drift. The moment you stop actively spending effort on order, the system starts losing it.

2. Why entropy always wins

The question was never whether your system turns into a mess. It’s when, and how fast.

A few things reliably speed it up.

Turnover. Everyone who leaves walks out with the context that was never written down — why a particular choice was made, which constraint forced it, the conventions nobody bothered to spell out. The code keeps the what. The why leaves with them.

Shifting requirements. An abstraction that fit perfectly in 2022 becomes a straitjacket in 2024. The data model that covered every case turns out too narrow, or too broad, the first time the product pivots. The code isn’t bad. It’s just no longer pointing where reality is.

Time pressure. The quick fix that was meant to be “temporary” is still there six months later. The duplication you were going to factor out “next week” is still waiting. That TODO from 2021 has become folklore.

Quiet accumulation. Call it the codebase’s hoarding problem. Nothing gets thrown away; things only get added. Feature flags that will never be cleaned up. Dead models kept around “just in case.” Dependencies solving problems you no longer have. Code nobody dares delete for fear of what it might be holding up.

Bit by bit the codebase turns into a museum of past decisions — some still valid, some long expired, and the line between the two getting blurrier as the context fades.

The goal isn’t to stop any of this. It’s to avoid the point where the pile gets so dense it freezes you — where every change is risky, nobody really follows the full flow anymore, and velocity drops to zero simply because no one can read it.

3. Don’t help it: the over-engineering trap

If entropy speeds up on its own, the worst thing you can do is lend a hand.

Over-engineering is the well-meaning developer’s paradox. You add complexity to “handle the future cases,” and you end up building exactly the unreadable system you were trying to avoid.

Questions worth asking first

Before you reach for another layer of abstraction, a pattern, or some elaborate error-handling machinery, stop and ask:

  • Do I actually need this now? Not “could this be handy someday,” but “does it solve a real problem I have today?”
  • How bad is it, really, if this error happens? Something that breaks once a week on an internal endpoint — does it warrant retries with exponential backoff and a circuit breaker?
  • What’s the full cost? Writing it is the visible part. Living with it — understanding it, explaining it, evolving it — is invisible and usually far larger.
  • When will I find out if I was right? If the answer is “once we have more users,” treat that as a warning.

Start small. Genuinely small.

Building in a tunnel — weeks or months of work before you show anyone anything — is one of the most dangerous habits a team can fall into. Not because people are slacking, but because without feedback you’re optimizing blind.

Ship to production as early as you can stand to. Ship something unfinished, with gaps in it. The real world will tell you what actually matters, and what it tells you is almost never what you pictured.

Then iterate, in small steps. Iterative development isn’t just one agile flavor among many — it’s how you keep the cost of being wrong low. The sooner you find out you built the wrong thing, the less of it you have to throw away.

Metrics earn their keep here. How many of these errors actually happen in a week? How many users does it touch? If you’re not measuring, you have no idea whether the effort you’re about to spend matches the size of the problem.

4. Nobody can see the future, including you

Engineers are forever tempted to call the future in advance.

“We’ll need it eventually.” “If we don’t do it now it’ll cost three times as much later.” “The business is definitely going to want this.”

Sometimes that’s true. More often it’s the comfortable illusion that you’re in control.

Requirements move fast, and rarely in the direction you bet on. The product that was going to be B2C ends up B2B. The feature you carefully prepared never gets asked for. The generic abstraction you built for “every future case” doesn’t cover the one real case that actually showed up.

The rule is boring and it works: build it when you need it, not before.

This isn’t a license to wing it. It’s an admission that getting the prediction wrong — dead code, abstractions that box you in, complexity added for nothing — usually costs more than refactoring later, once the need is real and in front of you.

YAGNI — You Aren’t Gonna Need It — is one of the oldest ideas in Extreme Programming and one of the most routinely ignored. Not out of laziness. Out of trying too hard.

5. A worked example: novehiclesinthepark.com

novehiclesinthepark.com is a small logic game built on one rule: “No vehicles in the park.”

Twenty-seven questions. Each one hands you a scenario. An ambulance cutting through the park for an emergency. A kid on a tricycle. A skateboard. A motorized wheelchair. Does the rule apply?

Try it. The discomfort sets in fast. Then come back and keep reading.

I’m borrowing the “No Vehicles in the Park” example here, but using it independently from its original analysis, which takes a different approach.

The instinct is to pin down “vehicle” by what it is — its intrinsic properties. Does it have an engine? Wheels? Is it motorized? Over some weight? Above some speed?

Every property you add settles one case and opens two more. The motorized wheelchair gets an exemption for accessibility. Fine — but then what about the electric delivery scooter? The gardener’s ride-on mower? So you refine, you bolt on conditions, you write exceptions to your exceptions. Six months later the rule reads like tax law: airtight on paper, impossible for a human to parse, and still beaten by the next case nobody thought of.

This is exactly what happens in a codebase. You define an entity — Invoice, User, Vehicle, Payment — by its fields, its types, its constraints. An edge case turns up. You add a condition. Then another. The validation service that was 50 lines is now 800, and nobody can say for sure whether it behaves correctly at the edges, because the code now encodes implicit rules stacked up layer by layer, none of which anyone ever sat down and decided on purpose.

There’s a result in mathematics that captures this with a slightly startling elegance: the Yoneda lemma.

In category theory it says an object is fully determined by its morphisms — by how every other object relates to it — and not by its internal makeup.

Put plainly: you don’t need to know what an object is. You need to know how everything interacts with it.

Bring that back to the park. Instead of hunting for a universal definition of “vehicle,” ask the right questions of the right people.

  • The park keeper cares about the safety of people walking through, the noise, the wear on the paths.
  • The emergency doctor needs to reach a patient in minutes, whatever the rule says the rest of the time.
  • The kid on the tricycle is playing. They’re not going anywhere, and they’re none of the risks the rule exists to prevent.
  • The person in the motorized wheelchair — ban their way of getting around and you’ve banned them from the park.

Map those interactions out — ask each person what they do with the concept rather than what the concept is — and the rule defines itself. It also holds up far better than any list of properties, because it’s anchored in how people actually behave instead of in cases you imagined.

The same reasoning applies to your code. Don’t open by defining Invoice with fifteen fields and three statuses. Open by asking what the accountant does with an invoice. What the client expects from it. What the tax system reads off it. Those interactions are what define the thing — the fields fall out of them naturally, and only the ones you actually need.

Ask questions. Not of yourself. Of the people who use it.

6. Where this leaves us

Entropy always wins. That’s not a failure of the team or a lapse in discipline — it’s a physical law showing up in complex systems. Disorder is the likeliest state. Order is something you have to keep earning.

What you do get to choose is how quickly you give up ground.

And the thing that quietly speeds it up is almost always the same: defining before understanding. Designing an architecture before you know how it’ll be used. Encoding business rules before checking them with the people who live by those rules. Building for an imagined future instead of an observable present.

Yoneda was right. An object only exists through its interactions. Your code isn’t defined by its architecture; it’s defined by what it lets its users do. Every bit of complexity added “for the future,” every abstraction built “for flexibility” — if it doesn’t concretely improve life for the people using the software, it’s just another layer of entropy you generated yourself.

What actually buys you time, roughly in order:

  1. Don’t write the code. The cleanest code is the code that isn’t there. At every layer, ask whether you need this now.
  2. Ask instead of assume. When you’re unsure what the business wants, the answer isn’t a generic abstraction — it’s a conversation with your PM, or better, with a real user.
  3. Ship early, iterate often. The real world corrects your assumptions faster than any design session.
  4. Talk to the people who use it. Not to ask what they want — they’ll ask for a faster horse. To watch what trips them up, what slows them down, what they need but can’t quite name. The software is for them, not for us.

Entropy always wins. Playing it well just means making sure it doesn’t win before you’ve had the chance to build something worth maintaining.

Acknowledgements

Thanks to Jean Helou and Thomas Pepio for their feedback and the conversations that shaped this piece.


메타데이터
post_id
cebd4ed15d6c
slug
entropy-always-wins-cebd4ed15d6c
url
https://blog.malt.engineering/entropy-always-wins-cebd4ed15d6c
canonical_url
https://blog.malt.engineering/entropy-always-wins-cebd4ed15d6c
author_url
https://medium.com/@stephen.begot_45980
status
ok
fetched_at
2026-07-09 20:42:47