“Let’s just make it configurable” sounds like a cheap compromise. At the implementation level, it often is: one more toggle, mode, or preference may take very little time. But for the product, the option does not end when another control appears in the UI. It adds another dimension to the system’s behavior — and from that point on, that dimension has to be designed, tested, observed, and supported.
What One Option Actually Adds
Imagine a product with two independent binary settings. Formally, that already gives us four combinations. Five such parameters give us 32; ten give us 1,024. This does not mean the team must literally test every combination, or that every combination is even possible: business rules, roles, platform constraints, or dependencies between parameters may rule some states out. But the direction matters: independent parameters do not just add to the state space. They can multiply it.
That is why a new setting rarely lives in isolation. Once it exists, we have to think not only about its own happy path, but also about how it behaves with existing modes, roles, subscription plans, platforms, feature flags, user data, and older client versions. The problem is not the toggle itself. The problem is the new combinations that the product now considers valid.
This is the kind of problem combinatorial testing tries to control. Instead of checking every possible configuration, it focuses on interactions between small groups of parameters — for example, pairwise or t-way combinations. The approach exists because exhaustive testing of a real configurable system becomes impractical very quickly.
State Space Is Not Just a Testing Problem
The easiest place to see the cost of configurability is QA, but the testing burden is only one part of it. For a product engineer, every option has a carrying cost: an ongoing cost of ownership that remains long after the pull request has been merged.
A new mode means another branch of behavior to consider during rollout, support, and debugging. Analytics may need to distinguish users in different configurations. Migrations may need to account for old values. Backward compatibility can become harder. A future change that would have been one behavior in a product without the setting may now require another question: what do we do with users who chose a different mode months or years ago?
Feature toggles show this clearly. The toggle itself is cheap, but conditional complexity builds around it: the code has to support two branches, tests have to cover both behaviors, and observability has to explain which configuration was active when a problem happened. A long-lived toggle can stop being just a rollout mechanism and become part of the product model.
Not All States Are Equally Real
It is important not to overstate the combinatorial explosion. The theoretical state space and the number of reachable states are not the same thing. If one option is available only to enterprise users, another exists only on macOS, and a third is automatically disabled in a certain mode, then not every mathematical combination makes sense.
But that does not remove the problem; it only makes it more interesting. The constraints themselves become part of the product. They have to be understood, documented, and preserved. The more parameters and dependencies we add, the harder it becomes to answer a simple operational question: what exact state is this user in, and why is the system behaving this way?
At this point, configurability starts to affect more than the code. It also affects the team’s ability to reason about the product. A simple state model is easier to keep in your head. A branching one requires more tooling, telemetry, test strategy, and discipline around the lifecycle of every option.
An Option Has to Earn Its Place
None of this means configurability is bad. For many products, it is part of the value: different users really do have different workflows, constraints, and preferences. Enterprise software with no configuration at all may not be simple. It may simply be unusable in a real environment.
The real question is whether a specific option creates enough value to justify the new surface area we may have to support for years. If a setting solves a rare edge case but adds another axis to the product model for as long as we support it, “giving the user a choice” can be more expensive than choosing one strong default.
This changes the way I think about the decision. Instead of asking, “How easy is it to add this setting?” it is more useful to ask, “What long-term product state are we creating with it?” The first question estimates today’s implementation cost. The second asks about the complexity budget the team will keep spending after release.
The Most Dangerous Word Here Is “Just”
“Just add a toggle.” “Just give the user a choice.” “Just make it configurable.” All three can be true from the point of view of the current ticket. Adding a control, a field to the model, and a few conditions really can be simple.
But the product does not end at merge. If the option stays, it becomes part of the states the system allows, the scenarios the team promises to support, and the amount of context someone will need to explain its behavior a year from now.
That is why I am less and less willing to treat configurability as a free compromise between two decisions. It is a product capability with its own cost. Before adding one more switch, I want to be sure we really want to own the state space it creates.
