SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: A feature flag with no removal date is a branch you cannot merge

· by Serguey Asael Shinder / Serguey Shinder

Feature flags are sold as the alternative to long-lived branches, and for the rollout they are. The pitch is exactly right: merge early, ship dark, turn it on for 1% of traffic, turn it off in seconds if it misbehaves. Nobody is arguing with that.

The problem is what happens on day 31.

What a flag actually costs after the rollout

The flag is still in the code. Both sides of it still compile, still have tests, still get refactored by people who have no idea which side is live. Every flag you keep:

That is a long-lived branch. It has the same cost — two versions of the truth, drifting — except the merge happens at runtime, in production, on a combination nobody tested.

The rule I use

A flag gets a removal date at the moment it is created, in the same commit. Not a ticket "to clean up later". A date, written next to the flag:

// FLAG: new-pricing-engine
// Owner: payments
// Remove by: 2026-11-15 (two weeks after full rollout)

And then one of two things is true on that date: the feature is fully on and the flag and the old branch are deleted, or the feature is not fully on, in which case the date gets moved deliberately, by a person, with a reason. Both outcomes are fine. Silence is not.

Serguey Asael Shinder: A feature flag with no removal date is a branch you cannot merge
A feature flag with no removal date is a branch you cannot merge — Serguey Asael Shinder

Making it mechanical

A comment is a good intention. Two cheap mechanisms turn it into a process:

A test that fails when a flag is overdue. Read the flag definitions, compare the removal date to today, fail the build with a list. It takes an afternoon, and it converts "we should clean up flags" into a red build that somebody must act on. Whoever moves the date is making a decision on the record.

A report of flag age and last change. Any flag that has been at 100% for a month is not a flag, it is a constant with extra steps. Any flag nobody has toggled since it was created is a branch nobody has merged.

The exception, and it is a real one

Some flags are permanent by design: a kill switch for an expensive dependency, a per-customer capability, a regional toggle for a legal requirement. Those are not flags in the rollout sense — they are configuration, and they belong in a different list with a different review, precisely so the temporary ones can be held to a date without arguing about them every time.

The distinction to insist on is simple: is this expected to be removed? If yes, name the day. If no, stop calling it a flag.

Related: the abstraction added for the second case is usually wrong.