Feb 02, 2026 7 min read

Iteration is the Key to Software

Feb 02, 2026

I’ve spent weeks designing database schemas that had to be migrated a month after launch. I’ve drawn service boundaries that looked clean on a whiteboard, only to find in production that they sat right across a busy transaction path.

It always comes back to the same thing: your understanding of the problem is lowest on day one. You know the requirements as they were written down. You don’t know what the data looks like at scale, which workflows users will ignore, or which ones they’ll actually use. So you guess. And when you guess wrong, you spend the next year maintaining an abstraction that solves a problem nobody has.

That’s not an argument for skipping the planning. You still need to think through your core data model, your security boundaries, the hard constraints you can’t iterate your way out of later — compliance rules, privacy requirements, hardware limits. That work matters. It just isn’t a substitute for shipping.

I ran into this on a block-based website builder I worked on. The goal was to let non-technical staff assemble pages from a component library without needing a developer for every layout change. We picked a reasonable stack, designed the admin interface carefully, and built something that worked. By the time it was ready, the people it was built for had found a different workaround and moved on. The requirement had shifted during development — not dramatically, just enough. A rough version in front of real users a few months earlier would have told us what to actually build, or whether to build it at all.

The answer is to ship the smallest thing that actually works — not a prototype you plan to throw away, but a functional core. Once it’s live, you stop predicting constraints and start seeing them. A slow read path shows up in the logs, so you add an index. A user flow turns out to be awkward, so you flatten it. You consolidate the services that kept talking to each other anyway.

Refactoring is the work

Refactoring feels like a penalty for getting the design wrong. It isn’t. It’s just the work.

“Plan to throw one away; you will, anyhow.” — Fred Brooks

You can only write the right abstraction after you’ve seen the actual duplication. Moving fast without ever cleaning up makes a mess. Iteration means shipping the core, watching how it behaves, and being willing to change things when it turns out you were wrong. A system holds up because the people working on it are willing to revisit old decisions.

Writing code that can change

This is where design principles are actually useful.

The one I keep coming back to is the Open-Closed Principle: a module should be open to extension and closed to modification. The failure mode is easy to describe.

When you have entities with different types, the obvious first move is a direct check:

if (item.type === "SUBSCRIPTION") {
    scheduleRenewal(item);
}

Fine for one type. Then the business adds another, and another. A year later you’ve got fifteen of these scattered across the codebase, and every new requirement means opening files that were already working and hoping you found all of them.

The fix is to move that logic into a behaviour object on the type itself:

// before: type identity leaks into every callsite
if (item.type === "SUBSCRIPTION") {
    scheduleRenewal(item);
}

// after: the type knows what it's capable of
if (item.type.behaviour.renewsAutomatically) {
    scheduleRenewal(item);
}

Adding a new type now means defining a new behaviour. The calling code doesn’t change.

Worth saying though: none of this belongs in version one. If you have a single type and no real sign of a second, a behaviour object is just indirection with no payoff — exactly the kind of premature abstraction the first half of this post is arguing against. Write the type check first. Let it exist. The signal to extract is when you’re wiring up the third variant and you notice the same conditional appearing in a second file. That’s the duplication the abstraction is actually solving. Extract then, not before.

Share