WierdX — Programming Reference All tutorials →
Developer reference · Practical tutorials · CS fundamentals
Engineering Practice

Feature Flags: Shipping Code Without Releasing It

Deploying code and releasing a feature to users don't have to happen at the same moment. Feature flags are the mechanism that separates the two, and they change how teams merge, test, and roll back.

Published July 6, 2026

A feature flag is a conditional check, usually reading a config value or calling a small service, that decides whether a piece of code runs for a given request:

if feature_flags.is_enabled("new_checkout_flow", user):
    return render_new_checkout(user)
else:
    return render_old_checkout(user)

That's the entire mechanism. Everything interesting about feature flags is in how teams use that mechanism, not the mechanism itself.

Deploy is not release

Without flags, merging a half-finished feature to the main branch means it either ships to every user or the branch sits unmerged for weeks, drifting further from main and making the eventual merge painful. With a flag wrapping the new code path, the feature can be merged and deployed to production while turned off, then enabled later with a config change — no new deploy required. This is the core benefit: deploying code becomes a low-risk, frequent, boring event, and releasing a feature becomes a separate decision made independently, sometimes by a product manager with no access to the deploy pipeline at all.

Progressive rollouts

Flags don't have to be all-or-nothing. A common pattern is percentage-based rollout: enable a flag for 1% of users, watch error rates and key metrics, then move to 5%, 25%, 100% over hours or days. If something goes wrong at 5%, only 5% of users were affected, and turning the flag back off is instant — no rollback deploy, no waiting for a build pipeline. This is meaningfully faster than a deploy-based rollback, which is why flags are often the first line of defense for risky changes even when the team also has normal CI/CD rollback capability.

Targeting rules beyond simple on/off

Real flag systems support targeting beyond a global boolean: enable a flag only for internal staff accounts (dogfooding before any customer sees it), only for a specific customer who requested early access, or only for users in a particular region where a legal requirement differs. The evaluation logic is the same is_enabled(flag, context) shape, but the context carries whatever attributes the targeting rules need — user ID, account plan, country, signup date.

Kill switches

A flag wrapping a risky new code path doubles as a kill switch: if the new checkout flow starts throwing errors at 2 a.m., flipping the flag off is a config change that takes effect in seconds, versus paging someone to run a rollback deploy. Some teams add kill switches around third-party integrations for exactly this reason — if a payment provider's API starts timing out, a flag can route around it (or disable that payment method entirely) without touching the deploy pipeline at all.

The cleanup problem

Flags accumulate. Every flag added and never removed is a permanent branch in the code — both code paths still need to work, still get tested (or don't, and rot), and still need to be reasoned about by anyone reading the function. A flag that's been at 100% for six months and will never go back to 0% isn't providing any more value; it's dead weight that makes the function harder to read and creates a second code path someone might accidentally re-enable.

Teams that manage flags well treat flag removal as part of the feature's definition of done, not an optional follow-up: once a rollout reaches 100% and stays there for an agreed period, removing the flag and the old code path is a required task, not a "someday" cleanup item. Without that discipline, a codebase after a year or two of heavy flag use accumulates dozens of permanently-true or permanently-false conditionals that nobody wants to touch because nobody's sure it's actually safe to delete the other branch.

Flags are not the same as A/B testing, even though they share a mechanism

A release toggle is meant to be short-lived: it exists to de-risk a rollout and gets deleted once the rollout completes. An experiment toggle is meant to persist for the duration of a test and route users into variants for comparison, with the split itself being the point rather than an incidental side effect of a gradual rollout. Conflating the two in one system is fine technically, but conflating them operationally — leaving an old A/B test flag in place after the experiment concluded, the way you might leave a completed release toggle — is exactly the kind of flag debt described above, just from a different source.