The Flexibility You Didn't Need
A particular kind of complexity feels like wisdom while you’re adding it. You’re building a thing that needs to do one job, and you think: but what if later we need it to do three? So you add a configuration option, a plugin point, a layer of indirection, a generalized interface — not because anything needs them now, but because someday something might. It feels responsible, like you’re saving future-you the trouble. What you’re actually doing is paying a certain cost today against an uncertain benefit tomorrow, and if you saw the odds written down, you often wouldn’t take the bet.
The cost is certain because the flexibility is liability the moment it exists, in exactly the sense the previous piece described. The config option has to be documented, tested across its values, and reasoned about by everyone who reads the code — including the combinations no one uses. The plugin architecture makes the simple case harder to follow because it’s wrapped in machinery for cases that don’t exist. The generalized abstraction is harder to understand than the concrete thing it replaced, and every future reader pays that understanding cost whether or not the generality ever gets used. You bought all of that on day one, in full.
The benefit is uncertain because you’re predicting the future, and predictions about what software will need are wrong most of the time. The three cases you generalized for turn out to be one case plus two you imagined; the real second case, when it finally arrives a year later, is shaped differently than you guessed, and your carefully built flexibility doesn’t fit it — so you end up reworking the generalization anyway, having paid to carry it unused in the meantime. This is the quiet cruelty of speculative generality: not only is the benefit often zero, but even when a real need shows up, the speculative version usually wasn’t the shape it needed, so you paid twice.
The discipline that answers this is the same “wait for evidence” instinct that governs abstraction generally: build for what you actually know you need now, and add flexibility when a real second case forces it, not when you imagine one might. The second real requirement teaches you the true axis of variation — which is almost never the one you would have guessed — so the flexibility you build then actually fits, because it’s shaped by a case that exists rather than one you invented. Concrete code that does exactly today’s job is easy to generalize later along the lines reality reveals. Prematurely general code is hard to change along any line reality didn’t happen to match.
None of this is an argument for painting yourself into corners or ignoring obvious near-term needs — if you know with confidence a second case is coming next sprint, building for it is just planning. The line is between flexibility you have real evidence you’ll need and flexibility you’re adding on a “might.” The former is prudence; the latter is complexity bought with a guess, and the guess is usually wrong. When you feel the urge to make something configurable, extensible, or general “just in case,” treat the “just in case” as the warning it is: you’re about to pay a certain cost for an uncertain return. Most of the time, the simplest thing that solves today’s problem is not naive — it’s the version that kept its options open by not spending them.