Skip to content
Richard Cooper
Go back

Feature flags as an architectural tool

A feature flag looks like the least architectural thing in the world. It’s an if statement with a dashboard bolted on — if (flags.newCheckout) { … } — the sort of thing you’d add in thirty seconds and never mention in a design review. Delivery tooling, not architecture. Something the DevOps people care about.

I want to argue the opposite: that reaching for a flag is one of the more consequential architectural decisions you make, precisely because it doesn’t feel like one. What a flag actually does is split two things that used to be welded together — and once you’ve split them, the shape of your decisions changes.

Deploy is not release

The two things are deploy and release.

Deploying means the code is on the server. Releasing means a user can see it. For most of the history of shipping software these happened at the same instant: the deploy was the release, which is why deploys were tense, why they happened at 2am, why “big bang” is a phrase we say with a wince. The moment the code went out, its behaviour went out with it, to everyone, at once.

A flag prises those two apart. The code deploys dark — present, exercised, but switched off — and you release it later, separately, by flipping a toggle. Deploy on Tuesday to nobody; release on Thursday to 5% of users; release to everyone the following week. The deploy stopped being the event. That’s not an operational convenience. It’s a change to what a deploy means, and that’s an architectural statement about your system.

It turns a one-way door into a two-way door

I’ve written before about one-way and two-way doors — the idea that how hard a decision is to reverse should govern how much you agonise over it. A deploy without flags is close to a one-way door: to undo it you roll back, redeploy, and hope nothing else moved in the meantime. Under pressure, mid-incident, that’s a genuinely frightening door to walk through.

A flag makes the same change a two-way door. “On” becomes a runtime decision you can reverse in seconds, without a build, without a deploy, without waking anyone. That single property changes the economics of the whole exercise. You can ship the risky thing behind a flag and decide in production, with real traffic whether it stays on — which is really just the last responsible moment applied to release. You defer the irreversible commitment until you actually have the information to make it, instead of guessing at build time and living with the guess.

It’s a cousin of deployment coupling, though not the same thing: there, separate components were forced to ship together because they shared a contract; here, a single change is split so that its deploy and its release come apart in time. Different couplings — but both are about keeping control of what actually goes live, and when.

Not all flags are the same thing

Here’s where it goes wrong, though, and where “just add a flag” stops being free. People treat “feature flag” as one concept when it’s really four, with wildly different lifespans and owners. Conflating them is the root of most flag messes I’ve seen.

The category error — managing a permission rule like a throwaway release toggle, or leaving a release toggle in place as though it were a kill switch — is how you end up unable to say what any given flag is actually for. And a flag nobody can explain is the dangerous kind.

Every flag is also a liability

Because the cost of a flag isn’t the if statement. It’s that every flag doubles the number of code paths, and they multiply: n independent flags describe up to 2ⁿ possible states, of which you meaningfully test approximately none. Your test suite runs one combination — usually “everything on”. Production runs the others.

The cautionary tale here is Knight Capital. On 1 August 2012 the firm rolled new code to its order-routing system but missed one of eight servers. The new code had reused an old flag — one that years earlier had switched on a piece of long-defunct test logic called Power Peg. On the seven updated servers the flag meant the new thing; on the eighth, still running the old code, it woke Power Peg back up. In roughly forty-five minutes the system fired millions of unintended orders and the firm lost about $440 million — very nearly its entire capital. It did not recover.

You can read that as a deployment failure, and it was one. But the deeper fault was a stale flag: a toggle left in the code long after its purpose had gone, still wired to logic nobody thought was reachable. A flag you didn’t remove isn’t a dormant control. It’s dead code wearing the costume of a control — and one day someone flips it.

The discipline: a flag is a decision with a lifecycle

So the architectural move isn’t “use feature flags.” It’s treating each flag as a decision that has a beginning and an end. In practice that means a few unglamorous habits:

If that sounds like the reasoning you’d capture in an architecture decision record, that’s because it is one. “We put this behind a flag because the blast radius justified a gradual rollout, and it comes out on this trigger” is exactly the kind of decision-and-its-expiry that the next person needs to inherit.

What you’re optimising for

As ever, it depends on what you’re buying and what you’re willing to pay. Flags buy you reversibility, a split between deploy and release, progressive rollout, and safe experimentation. They cost you complexity, an exploding test surface, and a standing obligation to clean up after yourself. That’s a trade worth making when the reversibility genuinely matters — a risky change, a wide blast radius, a migration you want to creep out one percent at a time. It’s a poor trade when you’re wrapping a change you’ll never turn off in a code path you’ll never remove, buying optionality you have no intention of using.

The flag itself was never the interesting part. The interesting part is that you’ve moved a decision from deploy time to run time, and from irreversible to reversible — and, like any architectural decision, it’s one to make deliberately and then follow through on, rather than sprinkle around and walk away from.


Share this post:

Previous Post
The risk equation
Next Post
Azure is insecure by default (by design)