A feature reaches full rollout. The dashboard is green. The team moves on. Six months later, an engineer changing the same code finds two implementations and a flag whose name nobody remembers choosing.
The problem is rarely that engineers forgot how to delete an if statement. The problem is that shipping had an owner and cleanup had a hope.
A temporary feature flag needs a removal owner before it needs a celebration. That owner is accountable for establishing when the release control has finished its job, proving that its old path can disappear, and getting the cleanup through deployment. An expiration date helps start the conversation. It cannot establish that removal is safe.
Separate temporary release flags from durable controls
Not every flag should disappear. A rollout flag may exist to expose a replacement gradually. An operational kill switch may remain useful for years. An entitlement may encode a contractual product capability. Those are different lifecycles, even when the same SDK evaluates all three.
Classify the control when you introduce it:
- Temporary rollout flag: moves traffic to a new implementation. Retire the evaluation and old branch after the rollout is complete and verified.
- Temporary migration flag: coordinates a transition between systems or representations. Retire transitional paths when the migration's completion conditions hold.
- Operational control: enables a defined response to operating conditions. Retain it with an owner and a tested response while that purpose remains useful.
- Entitlement: determines access to a product capability. Maintain it as supported product policy with explicit access semantics.
Calling everything temporary creates dishonest cleanup dates. Calling everything a kill switch creates permanent branches without an operating purpose. If the team wants to retain a rollout flag as a kill switch, make that an explicit design decision: what response does it enable, who can use it, and what does the disabled path still require?
A broken fallback does not become a recovery mechanism because the UI still has a toggle.
Put the retirement contract beside the release work
The removal owner can be a named engineer supported by an accountable team. Record both so a transfer or vacation does not leave an orphan. The owner coordinates the evidence and implementation; they need not personally change every consuming repository.
Keep the record close to the code or release issue. For example, this is an illustrative record, not a vendor configuration schema:
flag: search_v2_rollout
kind: temporary_release
accountable_team: search_platform
removal_owner: assigned_engineer
replacement_owner: team_technical_lead
purpose: move search to v2
review_trigger: >-
agreed observation window ends
after full rollout
removal_conditions:
- supported consumers use v2
- correctness checks satisfied
- operating checks satisfied
- recovery no longer needs old artifact
- references and dependencies inventoried
cleanup_issue: SEARCH-482
completion_evidence:
- cleanup deployment identifiers
- supported-version review
- recovery review
- reference scan
- configuration retirement review
Choose an actual observation window and checks for the change. A billing migration and a button redesign should not inherit the same arbitrary number of days. A review date is a reminder to make a decision, not an instruction to delete production configuration automatically.
The important fields answer operational questions: who will do the work, what event starts it, what blocks it, and what evidence closes it? If nobody can reserve capacity for cleanup, the release estimate is missing part of the release.
Full rollout is evidence, not the removal decision
A flag serving the new variation to every current request tells you something useful. It does not describe every environment, version, delayed job, prerequisite, or recovery artifact.
Before removing it, inspect:
- Production and nonproduction environments, including scheduled and infrequent workloads.
- Server, web, mobile, command-line, and worker consumers that may ship independently.
- Prerequisite flags, targeting rules, configuration templates, and operational scripts.
- Supported older clients and binaries retained as recovery targets.
- Tests that select a variation through mocks or fixture configuration.
A mobile application that has not upgraded is different from a branch that has not merged. A worker that executes monthly may not appear in a short traffic sample. An SDK default may activate when configuration is unavailable, even when ordinary evaluations always select the new behavior.
The rollback compatibility guide explains how changed production state constrains recovery. Flag retirement adds another constraint: an old artifact may still expect a configuration key or a fallback branch you are about to remove. Name the supported recovery target before retiring that dependency.
Use code references without confusing them with runtime inventory
LaunchDarkly's code-reference documentation describes scanning source repositories and reporting flag references. It distinguishes stale-flag detection from archive checks and explains extinction events tied to a commit where scanned references disappear.
That is valuable evidence. Its boundary is the source you scan and the matching rules you configure. It cannot, by itself, establish which artifacts remain deployed or which application versions customers still use. The documentation also explains literal key matching and configurable aliases; a wrapper that constructs keys deserves explicit attention.
For a literal key, a repository search is a useful first pass:
rg -n --fixed-strings \
'search_v2_rollout' \
src tests config scripts
Adapt the directories to your repository. Follow wrappers and aliases, inspect generated configuration inputs, and repeat across consuming repositories. Record the commit and scan scope. A clean result in one checkout is a statement about that checkout, not a universal absence claim.
Keep runtime inventory separate: deployment manifests, supported client-version policy, workload schedules, and evaluation evidence answer different questions. Agreement between these sources is much stronger than a green stale-flag badge alone.
Work through a concrete retirement sequence
Suppose a server-side search service uses search_v2_rollout to choose between two implementations. Release R12 supports both; the rollout has settled on v2. The following sequence is hypothetical, but the ordering exposes the decisions a real cleanup needs.
1. Establish the retained behavior
The removal owner verifies that v2 meets the agreed correctness and operating criteria. Review exceptional cohorts and other environments, not just the default production rule. Decide whether returning to v1 is still a supported response.
If v1 remains necessary, keep the flag and record why. Revisit the condition on a specific date. Do not archive it merely because the cleanup issue is old.
2. Ship code that no longer needs the flag
R13 calls v2 directly. Remove the unreachable v1 implementation and unnecessary evaluation plumbing. Keep tests for supported search behavior, error handling, authorization, and dependencies. Remove tests whose only purpose was asserting the retired branch selection.
Review SDK-unavailable behavior explicitly. Replacing a flag evaluation with a direct call can change the previous fallback. Verify that the intended new behavior still handles its own failures correctly rather than accidentally inheriting confidence from a removed toggle.
3. Complete deployment and recovery review
Deploy R13 through the normal process and confirm the relevant workers and environments have converged. If R12 remains an approved rollback artifact, preserve the flag configuration it needs or deliberately advance the recovery baseline to a verified compatible release.
Do not turn “no current evaluations” into “no possible evaluations” while an old client or recovery binary is still supported. Where older clients cannot be forced to upgrade, retain compatible configuration until their supported lifetime ends or implement a separately reviewed compatibility path.
4. Retire configuration and close the evidence
Once consumers and recovery requirements permit it, archive or delete the configuration using the provider's documented lifecycle. Check prerequisites and automation before destructive deletion. Remove obsolete dashboards, fixtures, documentation, and provisioning definitions so a future apply does not recreate the flag.
Attach deployment identifiers, scan results, and the recovery decision to the cleanup issue. Closing the issue means the retired path is gone from the supported system, not merely that a pull request merged.
Make cleanup normal engineering work
A weekly flag review should produce decisions: remove, retain with a stated purpose, or investigate a named dependency. Counting old flags without assigning work produces a report, not a maintained system.
Useful signals include temporary flags past their agreed review date, flags with no accountable team, and cleanup issues blocked by unsupported inventory. Use those signals to improve the release workflow. Avoid ranking individual engineers by flags deleted; a difficult cross-client retirement is not comparable to deleting a local experiment.
The safe development path article makes the broader case for supported workflows. Here, the useful default is small: creating a temporary flag also creates its retirement record and owned cleanup work.
Feature flags earn their complexity by making a transition safer. Once that transition ends, their remaining branches need an explicit reason to stay. Give someone responsibility for proving that reason has ended, and make removal part of finishing the feature.
Find more practical engineering guidance at Slaptijack.