The two previous pieces were really one argument seen from two angles. Code is a liability, not an asset, so of two solutions with the same behavior the smaller one wins. And flexibility you add on a guess is a certain cost against an uncertain benefit, so you should wait for a real second case before generalizing. Put together, they point at a single discipline: build the simplest thing that actually solves today’s problem, and resist the constant pull toward more. The pull is strong because more feels like diligence — more configurability, more layers, more handling of cases that haven’t happened. The discipline is learning to feel that pull and, most of the time, not act on it.

The trap is that “simplest thing that works” gets confused with “quickest thing I can get away with,” and they are not the same. The simplest thing that works still has to actually solve the problem — all of today’s real problem, including the error paths, the edge cases that genuinely occur, and the failure modes that matter. What it skips is everything speculative: the second use case no one has asked for, the configuration knob no one will turn, the abstraction built for variation that doesn’t yet exist. Simple is about scope, not about care. A shortcut cuts corners on the problem in front of you; simplicity cuts scope on problems you don’t have yet. One creates bugs; the other avoids liability.

This is why “simple” is genuinely hard, and why it so rarely happens by default. The naive version and the simple version can look similar in a diff, but they come from opposite places. The naive version is what you get when you stop thinking early — it’s underbuilt because you didn’t consider what could go wrong. The simple version is what you get when you think harder — you understood the problem well enough to see which parts are essential and which are imagined, and you deliberately built only the essential ones. Simplicity is a product of more understanding, not less effort. That’s the uncomfortable part: doing less well is more work than doing more badly.

There’s a useful test for which one you’re holding. Ask what happens when the requirements change. Naive code breaks in the face of cases that were always going to happen — it didn’t handle the empty input, the concurrent write, the timeout, and those weren’t hypothetical, they were the job. Simple code doesn’t break on today’s real cases because it handled them; it just doesn’t yet contain machinery for cases that haven’t arrived, and adding that machinery later is a clean, local change precisely because the code stayed concrete and small. If your “simple” solution falls over on inputs that reliably occur, it wasn’t simple, it was unfinished. If it handles what’s real and stays quiet about what isn’t, that’s the target.

None of this is a rule to write less code as an end in itself — it’s a way of deciding what’s worth building at all. Solve the problem you actually have, completely. Don’t solve the problems you imagine you might have, at all, until they become real, at which point they’ll teach you their true shape. The reward compounds: the codebase that only ever contained what it needed is the one that stays legible, stays fast to change, and never accumulates the quiet tax of features and flexibility no one uses. The simplest thing that works is not the lazy choice or the clever choice. It’s the disciplined one — and discipline, here, mostly means the restraint to stop once the real problem is solved and not one step sooner or later.