Sonnet Code
← Volver a todos los artículos
Engineering Practices3 de marzo de 2026·7 min read

Feature Flags Without the Mess: A Pragmatic Rollout Framework

The problem with the flag you added two years ago

Every codebase of meaningful size contains at least one feature flag that was supposed to be removed six sprints after launch and is still there, silently true in production, quietly referenced in code nobody remembers. Flags are the easiest reversible decision a team can make — which is exactly why they accumulate.

The cost is not the flag itself. It is the branching it creates in every code path it touches. A module gated by three old flags has eight possible states, most of which are never exercised in tests. When the incident finally happens, it is almost always one of the untested combinations.

Four flag types, four lifecycles

Teams that manage flags well name the type of every flag at creation, because the type determines its lifecycle:

  1. Release flags. Gate an unfinished feature. Lifecycle: delete within two weeks of full rollout.
  2. Experiment flags. Drive A/B tests. Lifecycle: delete when the experiment concludes, which should be on a fixed date defined at creation.
  3. Ops flags (kill switches). Disable a subsystem in an incident. Lifecycle: permanent, but audited quarterly.
  4. Permission flags. Gate functionality by plan or role. Lifecycle: permanent, but migrated to the authorization layer once the shape of plans stabilizes.

The mistake is treating all four the same. The lifecycles are different by nature, and the tooling should reflect that.

The flag creation template

At creation time, every flag gets a record with four fields: type, owner, removal criterion, and removal date. 'Removal criterion' is the specific condition that makes the flag safe to delete — not a calendar date, a state. 'The monolith caller is migrated,' or 'Experiment reports statistical significance,' or 'Rollout reaches 100% for 30 days.'

The removal date is a reminder, not a deadline. If the criterion has not been met by the date, the owner reviews and either extends or decides the flag is now ops-permanent. The act of reviewing is the thing that prevents the two-year-old zombie flag.

Where to evaluate, where to branch

The single most common flag design mistake is scattering if (flags.foo) across the codebase. Every call site becomes a place the flag definition can drift. Instead:

  • Evaluate flags at the edge of the request — ideally once, in middleware or the controller — and pass the resolved value into the rest of the code as plain configuration.
  • Branch at the boundary — ideally by injecting different implementations, not by sprinkling conditionals inside business logic.

The test matters. If you cannot write a clean unit test for both branches without stubbing the flag service, the design is wrong.

Progressive rollout that works

The rollout shape that actually catches bugs is staged but asymmetric:

  • 1% for 24 hours. Catches catastrophic regressions and crashes. No further ramp until the error rate is steady.
  • 10% for 48 hours. Catches performance regressions that only surface under moderate load.
  • 50% for 72 hours. Catches interaction bugs with adjacent features.
  • 100% with a visible rollback plan. Stays at 100% for at least a week before the flag is removed.

The durations are asymmetric because the failure modes at each stage have different signal-to-noise ratios. 1% catches loud failures fast. Higher percentages need time to surface quiet ones.

The one-page operational rule

Every flag has an owner. Every owner reviews their flags monthly. Every review ends with either 'delete,' 'keep with new removal date,' or 'promote to ops permanent.' There is no fourth option. The systems that apply this rule do not ship flag-driven outages. The systems that do not, eventually do.