Feature flags are branches that never merge
A long-lived feature flag costs the same as a long-lived branch. It causes divergence, dead code, and untested combinations. The difference is that a flag hides inside main, instead of sitting outside it.
Why flags feel free
A flag looks cheap to add. One conditional, one config entry, no merge conflict to resolve later. That is exactly what makes it easy to forget once it ships.
The combinatorial cost
Two flags mean four code paths. Three flags mean eight. Nobody tests all of them, so most combinations run in production untested, on real users, for the first time.
Flags need an expiry date
A flag without a removal date is a branch without a merge date. Set the date when you add the flag, not when someone trips over the old code. Whoever adds it owns the cleanup.
When a flag is the right call
Kill switches, gradual rollouts, and ops toggles are legitimate uses. They are meant to be short-lived or permanently operational, not a way to avoid deciding whether a feature ships.