Feature flags hygiene
A feature flag lets you ship code dark and turn it on when ready, splitting
deploy from release. The cost arrives later: every live flag doubles the
paths the code can take, and forgotten flags compound until no one knows
which branch runs in production. Treat each flag as debt with a due date.
Method
- Name for intent and lifetime. Prefix by kind:
release_, ops_,
experiment_, perm_. release_checkout_v2 states its own purpose;
flag_new_thing tells a maintainer nothing. Encode the owning team so an
orphan has a contact.
- Set an expiry at creation. Every release flag gets a removal date and
a cleanup ticket in the same pull request that adds it. A flag with no
death date becomes a permanent branch. Default release flags to 90 days.
- Default to off, fail to off. New flags start disabled. If the flag
service is unreachable, the code falls back to the old behavior, not to a
half-shipped one. Test the off path, since it is what most users run.
- Keep flag checks shallow. Read the flag once at the entry point and
branch there, rather than threading
if flag through ten functions. Deep
checks are the ones stranded when you finally delete the flag.
- Sweep on a cadence. Each sprint, list flags past expiry by crossing
grep -rn "isEnabled(" src/ against the flag dashboard. For each, remove
the flag and its dead branch, keeping the winning path.
- Delete the flag and the loser together. Removing a flag means
deleting the check, the config entry, and the code path that lost. A
"cleaned up" flag that leaves dead
else branches is half a job.
Checks
- Does every live flag have an owner, a purpose legible from its name, and
an expiry date?
- Are any flags past their removal date still in the codebase?
- If the flag provider errored right now, would the app serve the safe old
path?
Boundaries
Kill switches and long-lived operational toggles (ops_, perm_) are
deliberately permanent: exempt them from expiry sweeps but still name and
document them. Defer to your platform's flag tool (LaunchDarkly, Unleash,
Flagsmith) for storage, targeting, and audit history.
1---2name: feature-flags-hygiene3description: Ship code behind flags without accumulating flag rot, through disciplined naming, expiry, and cleanup sweeps. Use when adding a feature flag or auditing the flags already live in a codebase.4---56# Feature flags hygiene78A feature flag lets you ship code dark and turn it on when ready, splitting9deploy from release. The cost arrives later: every live flag doubles the10paths the code can take, and forgotten flags compound until no one knows11which branch runs in production. Treat each flag as debt with a due date.1213## Method14151. **Name for intent and lifetime.** Prefix by kind: `release_`, `ops_`,16 `experiment_`, `perm_`. `release_checkout_v2` states its own purpose;17 `flag_new_thing` tells a maintainer nothing. Encode the owning team so an18 orphan has a contact.192. **Set an expiry at creation.** Every release flag gets a removal date and20 a cleanup ticket in the same pull request that adds it. A flag with no21 death date becomes a permanent branch. Default release flags to 90 days.223. **Default to off, fail to off.** New flags start disabled. If the flag23 service is unreachable, the code falls back to the old behavior, not to a24 half-shipped one. Test the off path, since it is what most users run.254. **Keep flag checks shallow.** Read the flag once at the entry point and26 branch there, rather than threading `if flag` through ten functions. Deep27 checks are the ones stranded when you finally delete the flag.285. **Sweep on a cadence.** Each sprint, list flags past expiry by crossing29 `grep -rn "isEnabled(" src/` against the flag dashboard. For each, remove30 the flag and its dead branch, keeping the winning path.316. **Delete the flag and the loser together.** Removing a flag means32 deleting the check, the config entry, and the code path that lost. A33 "cleaned up" flag that leaves dead `else` branches is half a job.3435## Checks3637- Does every live flag have an owner, a purpose legible from its name, and38 an expiry date?39- Are any flags past their removal date still in the codebase?40- If the flag provider errored right now, would the app serve the safe old41 path?4243## Boundaries4445Kill switches and long-lived operational toggles (`ops_`, `perm_`) are46deliberately permanent: exempt them from expiry sweeps but still name and47document them. Defer to your platform's flag tool (LaunchDarkly, Unleash,48Flagsmith) for storage, targeting, and audit history.