A flag is a runtime branch with operational consequences. It can save a launch—or create two applications nobody knows how to test. The difference is discipline, not the flag provider.
Short answer: every flag needs an owner, a precise audience, a default-safe state, telemetry for both branches, and an expiry date. Release flags should be deleted after rollout; entitlement and kill-switch flags are different species and deserve different rules.
Name the job before naming the flag
| Flag type | Example | Lifetime |
|---|---|---|
| Release | new_checkout to 5% of users | Days or weeks |
| Kill switch | Disable a costly AI call | Potentially long-lived |
| Experiment | Compare two onboarding flows | Until decision is made |
| Entitlement | Enterprise-only export | Product lifetime |
Treating every flag as a permanent boolean is how if (flags.foo) spreads into thirty files with no one willing to delete it.
Roll out by evidence, not vibes
if (flags.enabled("new_checkout", { userId, plan })) {
return checkoutV2(request);
}
return checkoutV1(request);
Start with internal accounts, then a small stable cohort, then a wider percentage. Track the user-visible metric and the failure metric for both paths. A flag with no metric answers only “did we turn it on?”, which is not enough.
A flag is not a substitute for a migration plan. If the new path writes data the old path cannot read, the flag merely lets you choose when to break compatibility.
The deletion checklist
- Set the winning behaviour as the code default.
- Remove the losing branch and tests that only supported it.
- Delete the flag definition and dashboard targeting.
- Verify no clients cache an old value that changes safety behaviour.
The best feature-flag dashboard has a shrinking list. If a team cannot state the removal date at creation time, it is probably configuration or an entitlement—model it that way instead.
Cover photo by Nik Oak on Pexels.
