A temporary feature flag needs a removal condition when it is introduced. Give it an owner, identify the behavior that should remain, and define the evidence needed to delete the alternative path. Turning a flag on for everyone is an intermediate state: the old code, fallback configuration, and deployed versions may still depend on it.
The useful unit of work is the whole transition from two supported behaviors to one. Treat removal as the final change in the rollout, with its own compatibility check.
Decide what kind of control you are creating
Suppose an order service introduces a replacement total calculator behind use_new_totals. Its purpose is to expose a new implementation gradually while retaining the old one during evaluation. Once the replacement is accepted, retaining two calculators buys less protection and creates two places to maintain pricing fixes.
An emergency control that disables expensive recommendations has a different purpose. It may remain useful after the feature has shipped. A durable entitlement rule has another lifecycle again. Pete Hodgson's feature-toggle guide distinguishes release, experiment, operational, and permissioning toggles, and explains the carrying cost of retaining them. Classify the control before assigning its retirement rule.
For the calculator example, a proposed local record is enough:
key: use_new_totals
purpose: temporary release control
owner: order-service team
fallback: legacy calculator
review_on: 2026-11-15
remove_when:
- replacement behavior accepted for supported order types
- supported deployments no longer need the legacy route
- rollback plan no longer depends on changing this flag
This is a fictional record, not an approval or a universally supported configuration format. The review date initiates a decision. It should not silently flip a production switch or prevent a service from starting merely because a calendar deadline passed.
Keep the decision at one boundary
Resolve the flag once for the operation and pass the result to the code that chooses the implementation. If a long request evaluates a changing remote flag separately for validation, calculation, and persistence, it can combine assumptions from both implementations.
For this example, the decision belongs at the calculator boundary:
def total(items, use_replacement):
calculator = replacement if use_replacement else legacy
return calculator(items)
The caller decides use_replacement according to the rollout policy. The calculator does not need a flag-service client, user identity lookup, or percentage algorithm. This makes the two behaviors independently testable and gives the eventual removal a small surface.
Choose a stable assignment unit if users enter a percentage rollout. An account-level decision may be appropriate when several people share an order; request-level random selection could expose the same order to inconsistent behavior. This is an application choice to review, not a guarantee provided by the name “feature flag.”
Also define what happens when configuration is missing or unavailable. For an initial rollout, the legacy path may be the intended fallback. After broad adoption, that fallback can become the least exercised implementation. Keep its tests current while it remains reachable, or deliberately change the failure policy with corresponding evidence.
Advance through states that have observable exits
The following sequence is a proposed operating model for the calculator, rather than a claim that a particular flag platform implements it:
| State | What remains possible | Evidence before advancing |
|---|---|---|
| Introduced, off by default | Both routes exist; normal traffic uses legacy | Both routes satisfy the required input contract |
| Limited exposure | Selected accounts use the replacement | Outcomes can be compared by route without exposing customer data |
| Enabled for supported traffic | A fallback or old deployment may still use legacy | Supported order types and failure cases are covered |
| Removal candidate | Legacy is retained only for a named recovery path | Owner accepts a recovery plan independent of this flag |
| Removed | Current code uses the replacement directly | References, configuration consumers, and supported deployment versions are reconciled |
“No errors observed” is incomplete evidence if the new path received little traffic. For example, a calculator can work for ordinary quantities while never encountering returns or a boundary amount. Decide which inputs and operational signals matter before raising exposure. Record the selected observation period and its limitations; there is no universal number of days that establishes readiness to remove a flag.
The same reasoning applies to a rejected rollout. If the replacement is abandoned, remove its route and keep the legacy implementation. A flag does not have to end in the enabled state to have a completed lifecycle.
Test the deletion as a behavior change
We executed a small Python 3.14.2 fixture with two deliberately equivalent integer calculators. It tested an empty order, one line with quantity two, and two lines with different quantities. The old and replacement routes returned the expected totals, and the function after removal matched the enabled route for all three cases: nine assertions passed.
Input cases: 3
Assertions: 9
Old route, new route, removed route: passed
This is evidence for routing and removal equivalence on those inputs. It does not establish that a real pricing change is correct, that all combinations of flags work, or that a remote provider supplies configuration consistently. The fixture contains no discounts, tax rules, service calls, or percentage rollout.
A real deletion should retain the replacement's business tests and remove tests whose sole purpose was the obsolete branch selection. Keep a regression that covers the original reason for the replacement. Search for the flag key in application code, configuration, deployment templates, dashboards, and operational instructions. A search with no source references does not prove that every running binary stopped evaluating the key.
Remove code and configuration in a compatible order
An older binary may still ask the provider for use_new_totals. Deleting the remote key first could make that binary take its default, possibly reactivating legacy behavior. First deploy code that no longer evaluates the flag, confirm that the supported fleet and background consumers have moved to it, and then retire the configuration according to the provider's behavior.
Rollback complicates that order. If the retained previous artifact still expects the key, either preserve the key for that recovery window or choose a different recovery artifact. If the new calculator changed stored data, flipping a boolean is not necessarily a rollback at all. The dependency-update rollback guide explains why code and persisted state need separate recovery arguments.
Review the removal pull request with the same evidence standard as any other change. Our guide to reviewing generated pull requests is relevant whether a person or an agent performs the deletion: inspect the actual diff, compare behavior, and identify the boundary of the checks.
The completed record should explain which implementation remains, which deployments were considered, and how recovery works now. That gives the next maintainer a reason to trust the simpler code instead of recreating a switch whose purpose has been forgotten.
Documentation and synthetic local checks completed October 28, 2026. No flag platform or deployed service was changed.