Configuration Beats Customization: An Entropy Argument

The first if (customer == "ACME") is free.

The first if (customer == "ACME") is free.

It takes ten minutes to write, it unblocks a deal worth more than your salary, it is obviously correct as a business decision, and anyone who objects to it on architectural grounds is being precious. I have written this branch. You have written this branch. It was the right call.

The four-hundredth one is why your release cycle is six weeks and why nobody on the team can tell you what the login flow does without reading it.

What's interesting is that nothing changed between the first and the four-hundredth. Each individual decision was locally correct, made by competent people with good reasons, and approved by managers who were right to approve it. There is no bad decision in the sequence. And yet the outcome is a system that everybody agrees is bad.

That pattern — locally correct decisions producing a globally bad state, monotonically, with no obvious point of failure — is the signature of an entropy problem. I think the analogy is more than rhetorical, and taking it seriously produces a sharper rule than "avoid customer-specific code."

Configuration and customization are different in kind, not degree

The usual framing treats these as a spectrum: configuration is a bit of customization, customization is a lot of configuration. That framing is why the conversation goes nowhere, because it implies the question is how much, and the question is actually which.

Configuration selects a point in a state space you designed. The space is bounded, enumerable, and — critically — it was reasoned about as a whole. session_timeout: 30m picks one value from a range you already decided was valid. password_policy: strict selects one of three policies you already tested. You knew the space existed before any customer arrived, and the number of behaviours your system can exhibit did not change when the customer chose one.

Customization adds a dimension to the state space. if (customer == "ACME") skipEmailVerification() does not select from anything. It creates a new axis of variation that did not previously exist, that nobody enumerated, and that no test covers except the one written alongside it. The set of possible behaviours of your system is now strictly larger than it was yesterday, and larger than anyone has a model of.

That distinction is the whole argument. Configuration operates inside a designed space; customization enlarges an undesigned one.

And now the arithmetic, which is where the entropy language earns its place. If you have n independent binary customizations, the number of distinct behavioural states your system can be in is 2ⁿ. Not n. Twenty flags is a million states. Forty is a trillion. You are not testing those. You cannot test those. What you test is the handful of combinations your actual customers happen to occupy, which means the correctness of your system is not a property of your code — it's a property of which subsets of your flag space are currently populated, a fact that lives in a database owned by sales.

The parallel to statistical mechanics is exact enough to be useful: entropy is the log of the number of microstates consistent with what you can observe. Your test suite, your documentation, and your engineers' mental models are the macrostate. Each customization multiplies the microstates compatible with that same macrostate. High entropy doesn't mean the system is broken. It means you have lost the ability to know whether it is broken, which is worse, because it fails silently and only at customer sites.

Why it only ever increases

The second law's real content isn't that entropy is high. It's that entropy increases spontaneously and decreases only with deliberate work — and in software the asymmetry has a specific mechanism, which is worth naming because it's the thing you can actually attack.

Adding a branch requires local knowledge. Removing one requires global knowledge.

To add if (customer == "ACME"), one engineer needs to know one thing: what ACME asked for. The change is small, reviewable, and its correctness is checkable by the person making it.

To remove it, someone must know that no customer depends on it. Not "ACME doesn't" — no customer, including the eleven who were onboarded by a partner three years ago, including the one whose integration test suite passes only because of a side effect of this branch, including the one who will notice in nine months during their annual audit. That knowledge is distributed across a sales team, a support queue, a customer's undocumented integration, and several people who have left. Nobody possesses it, and no individual can acquire it in the time available.

So the cost of adding is O(1) and borne by a person with an incentive to add. The cost of removing is O(all customers) and borne by a person with no incentive at all — the engineer who deletes a flag gets nothing if it works and an incident if it doesn't. This is not a discipline problem. It is a gradient, and gradients don't care about your good intentions.

There's an information-theoretic version of the same point that I find clarifying. Adding a customer-specific branch is thermodynamically reversible at the moment you write it, because you still hold the information needed to undo it: you know why it's there, who asked, and what it's for. That information decays — the ticket gets archived, the requester leaves, the commit message says "fix for customer request." Once the information is gone, the branch is irreversible. It's not that removing it is expensive; it's that removing it safely is now impossible, because the state needed to compute the inverse operation no longer exists anywhere.

Which yields the single most actionable idea in this article: the moment of creation is the only moment when the information required to eventually delete it exists. Capture it then or never. A branch annotated with who asked, why, what breaks without it, and what condition would let it die is a reversible branch. One that isn't is permanent from the day it merges, regardless of anyone's intent.

The costs that don't show up in the estimate

When someone estimates a customer-specific feature, they estimate the code. The code is the cheapest part. The bill arrives in four other places.

Every future change pays a tax proportional to accumulated entropy. This is the compounding one and it's why the trajectory feels fine and then suddenly doesn't. Refactoring the login flow when it has one variant is a two-day job. With forty conditional variants, it's a two-week job with an unquantifiable risk of breaking one of them, which in practice means it does not happen. The system ossifies — not through any single decision, but because the expected cost of every improvement has risen above the expected benefit. Teams describe this as "the codebase is a mess." It's more precise to say the marginal cost of change has exceeded the marginal value of change, and that's a computable quantity if you care to compute it.

Benchmarks and performance reasoning stop meaning anything. "Login takes 40ms" is a statement about a code path. With per-customer variation, there is no code path; there are four hundred, and the one your largest customer runs has two extra network calls in it. Your p99 dashboard is an average over a population you didn't design.

Onboarding cost grows with the log of the state space. A new engineer cannot learn what the system does by reading it, because what it does depends on configuration data in production. The knowledge is no longer in the codebase. This is the point at which teams start saying "you have to ask Sarah," and Sarah becomes a single point of failure with a mortgage and a LinkedIn profile.

Security review becomes unsound. This is the sharpest cost in any domain with a security boundary, and identity is only the clearest example. A reviewer approves an authentication flow. What they actually approved is the flow as it executes under the default configuration. The variant where a customer-specific branch skips a verification step was not reviewed, because it isn't visible from the code the reviewer read — it's one of the 2ⁿ states. Every security property you claim is implicitly conditioned on a configuration nobody enumerated, which means the honest form of your claim is "this is secure for some customers."

The counterpoint: when forking is correct

An argument that only ever says no is not an argument, it's a temperament. There are cases where the customer-specific path is right, and confusing them with the general case is how architects lose the trust of the people who have to close deals.

When the requirement is a different product. If a customer needs behaviour that contradicts a design premise — not extends it, contradicts it — then flattening both into one codebase produces a system that is coherent for neither. A genuinely separate deployment, with its own release train and its own team, is cleaner than a branch. The test: does satisfying this requirement mean the documentation now has to say "except when," in a place where "except when" makes the concept harder to explain? Then it's a different product, and you should price it as one.

When the divergence sits on an existing seam. A customization that lives behind an interface you already have — a pluggable storage backend, a notification transport, an authentication method — adds a variant to an axis that already exists and is already tested as an axis. That's much closer to configuration than to customization, and the entropy cost is roughly the cost of one more implementation rather than one more dimension. The seam is doing the work. Which suggests a useful move: when a customization request arrives, ask whether a seam is the deliverable, rather than the branch.

When the contract outlives the code. A five-year government contract with fixed requirements against a codebase that will be rewritten in three years is a case where you are not, in fact, taking on a permanent tax. Forking is fine. Be honest that this is the reasoning, though, because "we'll rewrite it" is also the most common lie in software.

When the cost of the branch is genuinely bounded and you can prove it. A branch in a leaf function with no interaction with anything else, covered by a test, with a documented owner and an expiry date, is a small permanent cost. Some of those are worth paying. The failure mode isn't paying them; it's paying them without counting.

The distinguishing question across all four: is this variation on an axis, or is it a new axis? Variation on an existing axis is cheap and roughly linear. New axes multiply.

Turning the ratchet the other way

Entropy decreases only with work, so the practical question is where to spend the least work for the most reduction.

Make every second request a design trigger, not a second branch. The first customer asking for something is a request. The second is data. When request #2 arrives for the same shape of thing, the correct response is not another branch — it's to design the axis: give it a name, define its valid values, decide the default, document it, test the values. You have now converted an unbounded dimension into a bounded one, and every subsequent customer selects from it for free. This is the single highest-leverage habit available, and its effect is enormous: it caps n at the number of shapes rather than the number of customers.

Require an expiry or review date on every customization, enforced by tooling. Not a comment — a field in something that can fail a build or open a ticket. The mechanism is inverting the burden of proof: instead of an engineer having to prove a flag is unneeded before removing it, someone must periodically claim it's still needed or it dies. Unclaimed things dying on their own is the only cleanup mechanism that has ever worked at scale, in any system, including biological ones.

Instrument the branches. You cannot remove what you cannot observe. Emit a counter tagged with the flag every time a customer-specific path executes. Six months later, the flags with zero executions are free deletions — not guesses, evidence. This converts the O(all customers) knowledge problem into a query, which is the only way it ever gets solved. It's the same move as last-used telemetry on credentials, and for the same reason: confidence, not capability, is the scarce resource in any deletion.

Price customization at its true cost, out loud. If a customer-specific feature is a permanent tax on all future development, then it should be quoted as recurring, not one-off. Engineering's job here isn't to say no; it's to make the cost visible so that the person with the authority to trade revenue against velocity is making an informed trade instead of an invisible one. Most of the time they'll still say yes — and that's fine, because a knowingly-purchased cost is a completely different thing from an unknowingly-accumulated one.

Spend the work you save on making configuration expressive. The reason teams reach for customization is usually that configuration couldn't express the intent — and often that's because the configuration surface models the implementation rather than the intent. Config that exposes protocol vocabulary forces customers into "just let me write a hook" the moment they need something the vocabulary can't say. Config that expresses intent — a policy, a risk posture, a connection — absorbs far more requirement per axis. Expressive configuration is the substitute good for customization, and if yours is weak, you are going to lose this argument on the merits every time.

The line

Here's the version to carry around, applicable well outside identity platforms — it's the same argument for feature flags, plugin systems, per-tenant overrides, and every internal tool that grew a special case for one team:

Configuration is a choice within a space you understand. Customization is an expansion of a space nobody understands. The first is bounded; the second compounds; and the second is only ever removable while you still remember why it's there.

The four-hundredth if isn't the problem. The problem is that when it was the fourth, nobody wrote down why — and now the information required to reverse it is gone, so it will be there forever, and every change your team makes for the rest of the product's life will be slightly more expensive because of it.

Write down why. That's the whole discipline.