← Academy index
Chapter 6 · 11 min read

Managing Flag Debt

Every flag is a debt. How to sunset the right ones at the right time, the half-life of a release flag, and the audit habits that keep a flag system from rotting.

operationsmaintenancedebt

A feature flag is a loan. You take it out to ship something safely. You pay it back by deleting the flag once the feature is fully launched. The problem with loans is that the interest compounds, and the problem with feature flags is that the interest compounds invisibly, in branching test matrices, in unused code paths, in the slow erosion of confidence about what the system actually does.

A two-year-old codebase with 300 unmanaged flags is one of the most painful technical-debt patterns there is. This chapter is how not to get there.

The four classes of debt

Not every flag is equally rotten. Sort them honestly.

Class 1: Released but not deleted

The feature is at 100%. Nobody is going to flip it back. The flag is dead code wearing a small wig of configurability. Delete it.

Half-life: weeks. If a release flag has been at 100% for 30 days, it should be on the chopping block.

Class 2: Stuck at <100%

A rollout started, made it to 25%, and stopped. The PM moved on. Nobody remembers whether the next step is “ramp up” or “kill it”. The flag is now silently picking who gets which experience based on a decision nobody owns.

Half-life: days, once you notice. Either finish the rollout or abandon it.

Class 3: Permanent but unowned

Kill switches and entitlement flags are supposed to be permanent. But “permanent” doesn’t mean “set and forget”. Without an owner, they bit-rot. The condition they protect against changes. The code under the “off” branch breaks silently.

Half-life: every quarter. Every permanent flag needs a review: still relevant? off branch still works? owner still at the company?

Class 4: The orphan

The flag exists. No code references it. (Or vice versa: code references it; the flag doesn’t exist.) Both states are bugs.

Half-life: instant. A static-analysis run should be CI-blocking on this.

The half-life of a release flag

Empirically: most release flags should be deleted within 30 to 90 days of reaching 100%. Beyond that, the cost starts to outweigh the benefit:

  • Every flag is a branch in your test matrix. Two flags = 4 paths. Ten flags = 1024 paths. Nobody tests 1024 paths.
  • Every flag is one more thing a new engineer has to learn.
  • Every flag is a small attack surface, wrong default, wrong rollout, wrong segment.

A useful rule: a flag isn’t shipped until it’s deleted. The PRD isn’t done. The story isn’t closed. There’s a step after “rolled out”: “removed from the code”. Get that step into your team’s definition of done and the debt problem goes away mostly on its own.

A working sunsetting playbook

The mechanics, in order:

  1. Set a target date when the flag is created. Most flag systems let you set an expiration. Use it. Default to 90 days from creation.
  2. At 100% rollout, set a delete date. 30 days out, by default. Mark the flag as “ready for cleanup”.
  3. At delete date − 1 week, an automated reminder. Slack message, email, whatever fits. Names the owner.
  4. Pull the code references first. Replace the flag check with the winning branch. Delete the loser.
  5. Then delete the flag. Not before, you want the flag to still exist in case something goes wrong between PR and deploy.
  6. Confirm in the next sprint review. “We deleted these N flags this sprint” should be a recurring agenda item until the backlog is empty.

This is the same shape as paying down credit cards: small, regular, boring. The team that runs a 30-minute “flag cleanup” once a sprint will never have a debt problem. The team that “will get to it” will have 300 flags.

Audit habits

Once a quarter, or more, if you’re moving fast, run a real audit. The questions:

  • How many flags exist? If the number is growing faster than your engineering team, the loop above isn’t working.
  • What’s the oldest active flag? Anything older than a year that isn’t a kill switch or entitlement: investigate.
  • What’s the largest rollout that isn’t at 0% or 100%? Either of those is a clean state. Anything in between is a half-finished thought.
  • Which flags haven’t been evaluated in 30 days? They’re either dead code or the code calling them is dead. Either way, prune.
  • Which flags have no owner? Assign one or delete.

A flag system worth using will surface these as built-in reports. (ShipSilently does, the audit log and “stale flag” view are first-class.) If yours doesn’t, write a script. The reports are not optional.

Naming hygiene

This sounds petty until you’ve debugged a wrong-flag-flipped incident at 3am. A convention worth borrowing:

<type>.<area>.<verb>_<noun>
  • release.checkout.enable_apple_pay, a release flag.
  • ops.payments.disable_stripe, a kill switch.
  • exp.signup.headline_test_q3, an experiment, dated.
  • ent.plan.feature_analytics_pro, an entitlement.

Now grep ops. shows your kill switches. grep exp. shows your live experiments. grep release. shows what you should be deleting. The taxonomy is free; impose it on day one.

The cleanup PR culture

The deepest fix for flag debt is cultural: cleanup PRs are first-class work, not chores between “real” tasks. That means:

  • Cleanup PRs get reviewed promptly, not after the “important” PR.
  • The engineer who shipped the feature is responsible for the cleanup PR; it doesn’t get handed off to “platform” or “whoever has time”.
  • Cleanup PRs ship in their own commit so they show up cleanly in the audit trail. Don’t sneak them into a feature PR.
  • “Removed flag X” is a celebrated kind of changelog entry, not a hidden one.

Teams that get this right have flag systems that stay healthy forever. Teams that don’t accumulate the debt regardless of how good their tooling is.

The longer write-up: Managing feature flag technical debt. And the cautionary tale of what happens when you don’t: Vibe-coded feature flags.

A working answer to “how many flags is too many?”

There is no universal number. There is a useful ratio: active flags ÷ engineers. If it’s under 5, you’re probably fine. Between 5 and 15, you should have a sunsetting habit. Over 15, something is broken, either the cleanup loop isn’t running, or you’re using flags for things that should be config, or the team has stopped thinking of flags as debt.

The instinct of every engineer who first encounters a flag system is to use them for everything. The discipline is to remember: a flag you delete cost you nothing. A flag that lingers will cost you for years.


Previous: Chapter 5, Experimentation vs feature flagging  ·  Next: Chapter 7, Trunk-based development with flags