State Is the Hard Part
If you look closely at where the difficulty in software actually lives, it’s rarely the computation. The pure logic — given these inputs, produce this output — is usually the part you can reason about, test in isolation, and get right. What’s hard is state: the accumulated memory of everything that happened before this moment. A function that transforms its arguments into a return value is easy to understand. The same function reaching into a variable that some earlier event set, whose value depends on a sequence of things that may or may not have happened, is where the reasoning gets slippery and the bugs breed.
The reason state is so much harder than computation is that it multiplies. Each independent piece of mutable state doubles the number of configurations the system can be in; a handful of booleans and a couple of nullable fields, and you already have more combinations than anyone will ever enumerate. Most of those combinations you never consciously designed — they just became reachable, quietly, the moment the state existed. And bugs love exactly the combinations nobody pictured: this flag set while that field is still null, this cache populated but that one stale, this request arriving while the system is halfway through a transition it assumed was atomic.
This reframes what most debugging actually is. When something misbehaves and the logic looks correct on inspection, it usually is correct — for the states you imagined it running in. The failure is that the system reached a state you didn’t imagine, and the code met it there with behavior that made sense for a different situation. You’re not hunting a wrong calculation; you’re hunting a combination of values that shouldn’t have been able to coexist and did. The question “how did it get like this?” is the real work, and it’s hard precisely because the current state is the residue of a history you can’t directly see.
Which is why so much good design is, underneath, an effort to have less state or to tame the state you can’t avoid. Making things immutable removes the axis of “what was it before” entirely — a value that never changes has no history to reason about. Deriving data instead of storing it means there’s no second copy to fall out of sync with the first. Keeping a single source of truth means a fact lives in one place, so it can’t disagree with itself. None of these are about doing the computation better; they’re about shrinking the space of states the system can occupy, because a smaller space is one you can actually hold in your head and check.
The mental shift is to stop treating state as free and start treating each mutable, independent piece of it as a real cost — because every one you add expands the set of situations your logic has to be correct in, most of which you will never explicitly consider. The most robust systems aren’t the ones with the cleverest logic; they’re the ones with the least room to be in a bad state, where invariants are enforced in one place and illegal combinations are made unrepresentable rather than merely avoided. Get the state model right and the logic tends to fall out easily. Get it wrong and no amount of careful computation saves you, because the bug was never in the computing — it was in what the system was allowed to remember.