Every feature flag needs a deletion date
The claim A feature flag is a deployment tool with a lifespan measured in weeks. A flag that survives past its launch becomes permanent configuration nobody understands, and a code...
The claim
A feature flag is a deployment tool with a lifespan measured in weeks. A flag that survives past its launch becomes permanent configuration nobody understands, and a codebase with forty stale flags is not one application — it is two to the fortieth possible applications, of which you have tested exactly one. The fix is procedural, not technical: no flag enters the codebase without a removal ticket, an owner, and a date.
What flags are for, and for how long
Three legitimate uses, with three different honest lifespans:
- Release flags separate deploying code from enabling it. You merge small increments behind the flag, turn it on for staff, then a percentage, then everyone. Lifespan: days to a few weeks, deleted after full rollout plus one calm week.
- Operational switches let you shed load or disable an expensive subsystem during an incident — turn off recommendations, drop to a static homepage. Lifespan: permanent, but there should be fewer than ten of them, each documented in the runbook and tested quarterly, because an untested kill switch fails exactly when you reach for it.
- Experiment flags support an A/B test with a defined end date. When the experiment concludes, the losing branch is deleted — not disabled, deleted.
The failure mode is treating everything as the second category. It is always easier to leave a flag in "just in case", and each individual decision is defensible. The sum of those decisions is a system where nobody can say with confidence what is actually running in production.
The cost, made concrete
Count yours right now:
grep -rEo "isEnabled\(['\"][a-z0-9_-]+" app/ | sort -u | wc -l
Then answer three questions for the five oldest: who owns it, what happens if it is toggled today, and when was the off-path last executed? In most audits we run, nobody in the building can answer the third question for flags older than six months. That off-path is untested code reachable from production configuration — the same risk profile as a dead button wired to live equipment.
Stale flags also interact. Two flags produce four states; a checkout flow with six flags has sixty-four, and the bug report that eventually arrives will reproduce in exactly one of them.
The lifecycle that prevents it
- Creation requires three fields: an owner, an intended lifespan, and a linked removal ticket, created in the same pull request that adds the flag. The ticket is scheduled for the sprint after full rollout, not "later".
- Naming carries the date:
rel_2026_09_checkout_v2. A flag named after its creation month advertises its own staleness in every code review. - Rollout completes with a deletion, not a setting. The definition of done for the feature includes removing the flag and the old branch. The feature is not finished while the flag exists.
- A monthly report lists flags past their date, sorted oldest first, sent to the team channel. Social pressure does the rest; nobody wants to own the top line for three consecutive months.
Keep the mechanism boring
For most businesses, a flag system should be a table and a cached lookup:
CREATE TABLE feature_flags (
key text PRIMARY KEY,
enabled boolean NOT NULL DEFAULT false,
pct_rollout smallint NOT NULL DEFAULT 0 CHECK (pct_rollout BETWEEN 0 AND 100),
owner text NOT NULL,
remove_by date NOT NULL
);
Percentage rollout is a hash of the user ID against pct_rollout — deterministic, so a user does not flip between variants on every request. Cache the table in-process for thirty seconds so the flag check costs nothing on the hot path.
Hosted flag platforms earn their fee when you need audit trails, targeting rules by attribute, or non-engineers changing flags safely. They do not remove the discipline problem; they give the stale flags a nicer dashboard to be stale in. Adopt the lifecycle first, the platform second if at all.
One rule for the flags that guard money
Any flag that changes billing, pricing, tax treatment, or payment routing gets logged every time it is read with its value, and its state is included in the order record itself. When finance asks in November why a March invoice was calculated the old way, "the flag was probably off" is not an answer. The order saying pricing_v2: false is.
The test of a healthy system
Ask how many flags exist and watch how long the answer takes. A team with a healthy flag culture answers from a dashboard in ten seconds and the number is under fifteen. A team that has to grep, and finds sixty, has been shipping configuration as a substitute for decisions — and every one of those sixty is a decision still waiting to be made.