A feature flag is a conditional in your code that lets you turn functionality on or off at runtime—without deploying new code. That one capability decouples deploying code from releasing features, which unlocks safe gradual rollouts, instant rollback, A/B testing, and the ability to merge unfinished work without breaking anything. This guide explains how feature flags work, the four types you'll encounter, what they let you do, the tools that manage them, and the flag debt that bites teams who don't clean up.
What a feature flag is
A feature flag (also called a feature toggle) is, at its simplest, an if statement that decides whether a piece of functionality runs. Instead of new behavior being controlled by which code is deployed, it's controlled by whether a flag is on:
if (flags.isEnabled("new-checkout")) {
renderNewCheckout(); // the new code path
} else {
renderOldCheckout(); // the existing, safe path
}
The important word is runtime. The flag's value is read while the program runs, from configuration outside the code—so you can flip a feature on or off without shipping a new build. Both code paths are present in the deployed application; the flag chooses between them on the fly.
This is the core idea everything else builds on: deploying code and releasing a feature become two separate events. You can deploy the new checkout to production today with its flag off—dormant and invisible—then turn it on next week, for 5% of users, or only for your internal team, all without another deployment.
The main types of feature flags
Not all flags serve the same purpose, and conflating them is a common source of mess. The widely used taxonomy (popularized by Pete Hodgson) sorts flags by two questions: how long they live, and how dynamically their value changes.
| Type | Purpose | Lifespan | Changes |
|---|---|---|---|
| Release toggle | Hide in-progress features so unfinished code can ship | Short (days–weeks) | Rarely, then removed |
| Experiment toggle | Run A/B or multivariate tests | Medium (weeks) | Per user, dynamically |
| Ops toggle | Operational control; kill switches, load shedding | Varies; some permanent | Occasionally, by operators |
| Permission toggle | Gate features by user (beta, premium, internal) | Long-lived/permanent | Per user, by entitlement |
Release toggles are the most common and the most dangerous to leave lying around—they exist only to let unfinished work merge safely, and should be deleted once the feature fully ships. Experiment toggles route different users to different variations to measure impact. Ops toggles give operators a switch to disable a heavy or risky feature under load—a kill switch is an ops toggle. Permission toggles decide which users get which features and often live forever by design, like gating a premium tier. Knowing which type you're creating tells you how to manage its lifecycle.
What feature flags let you do
The payoff of separating deploy from release is a set of capabilities that are hard or impossible otherwise.
Merge unfinished work safely. A half-built feature can sit behind a release toggle in the main branch, deployed but switched off. This is what makes trunk-based development and short-lived branches practical—developers integrate small changes continuously instead of nursing long-lived branches, because incomplete features simply stay dark.
Roll out gradually and run canaries. Rather than flipping a feature on for everyone, you enable it for 1%, then 10%, then 50% of users, watching error rates and metrics at each step. This pairs naturally with the deployment strategies in a CI/CD pipeline, giving you a release valve you control independently of deploys.
Kill instantly without a rollback. If a newly enabled feature misbehaves, you flip its flag off—in seconds—rather than scrambling to redeploy the previous version. The bad code is still deployed; it's just no longer reachable.
Experiment. Show variant A to half your users and variant B to the other half, then measure which performs better. Feature flags are the mechanism behind most product A/B testing.
Coordinate risky changes. Flags let you separate a database change from the code that depends on it. You can deploy a schema migration with one of the database migration tools worth comparing, keep the new code behind a flag until the migration finishes, then enable the feature—deploying schema and behavior independently and rolling back cleanly if needed.
Implementing and managing flags
Getting started is easy; doing it well over time is the real skill.
From hardcoded to managed
The naive version—a hardcoded constant—works for exactly one toggle and then becomes unmaintainable:
const ENABLE_NEW_CHECKOUT = false; // requires a redeploy to change
The next step is reading flags from runtime configuration so you can change them without deploying. Beyond that, a real flag system evaluates flags per request with context, enabling targeting and percentage rollouts:
// Evaluate for this specific user via a flag-management SDK
const show = flags.isEnabled("new-checkout", {
userId: user.id,
country: user.country,
plan: user.plan,
});
if (show) renderNewCheckout();
// The rollout rule (e.g. "10% of users" or "beta group only")
// lives in the flag service's config, not in your code.
Build versus buy
For a couple of simple on/off switches, environment variables or a config file are fine. Once you need percentage rollouts, per-user targeting, an audit trail, and a UI for non-engineers to flip flags, a dedicated flag-management platform earns its keep. Open-source options like Unleash, Flagsmith, and GrowthBook can be self-hosted; commercial services like LaunchDarkly (the market leader), Split, and ConfigCat offer hosted SDKs, targeting, and analytics. The decision hinges on scale, compliance needs, and whether you want to run the infrastructure yourself.
Flag debt: the part teams underestimate
Every flag is a branch in your logic, and branches multiply. Left unmanaged, stale flags accumulate into flag debt: dead code paths nobody remembers, configuration sprawl, and the genuine risk that someone flips a forgotten flag and breaks production. The discipline that prevents it:
- Treat release toggles as temporary. Create them with a removal date in mind and delete them—and the dead code path—once the feature is fully rolled out.
- Give every flag an owner and a purpose. An unowned flag is a flag nobody will ever dare remove.
- Track and review flags regularly. Many teams enforce cleanup in their pipeline, using a step in GitHub Actions or another CI tool to flag toggles older than a set age for review.
- Test both paths. Both the on and off states ship to production, so both need testing. Don't let the off path rot.
Common mistakes to avoid
Never removing release flags. The single biggest mistake. Temporary toggles that become permanent turn your codebase into a maze of dead branches. Schedule their removal.
Combinatorial flag explosion. Many interacting flags create an unmanageable number of possible states, most of which you've never tested. Keep flags independent and few.
Putting secrets or core logic in flags. A flag is a switch, not a config store or a security boundary. Don't use it to hold credentials or enforce real authorization.
No targeting context. Flags evaluated without user context can only do all-or-nothing switches, losing the gradual-rollout and experiment benefits. Pass identifying context to the evaluation.
Testing only the happy path. If you only ever test with flags in their final state, the fallback path breaks silently. Test both states, especially the off path you'll rely on in an emergency.
Treating a flag as a substitute for a real rollback plan. Kill switches are powerful, but some changes—like a destructive migration—can't be undone by flipping a flag. Know which is which.
Frequently asked questions
What is the difference between a feature flag and a feature toggle? They're the same thing—two names for a conditional that turns functionality on or off at runtime. "Feature flag" and "feature toggle" are used interchangeably, along with terms like feature switch. The concept is identical regardless of the label.
How do feature flags help with deployment? They decouple deploying code from releasing features. You can deploy code to production with a feature switched off, then turn it on later—for everyone or a subset of users—without another deployment. This enables gradual rollouts, instant kill switches, and merging unfinished work safely.
Do I need a tool like LaunchDarkly to use feature flags? No. A simple flag can be a configuration value or environment variable you read at runtime. Dedicated platforms—LaunchDarkly, Unleash, Flagsmith, Split, and others—become worthwhile when you need percentage rollouts, per-user targeting, audit logs, and a UI for changing flags without deploying.
What is feature flag debt? It's the accumulation of stale, unused flags that linger in the codebase after they're no longer needed. They create dead code paths, add complexity, and risk someone toggling a forgotten flag. The fix is treating release flags as temporary and removing them—and their old code—promptly.
Are feature flags only for large companies? No. Even a solo developer benefits from being able to ship dark, roll out gradually, and kill a broken feature instantly. Small teams can start with simple config-based flags and adopt a managed platform only if their needs grow.
The takeaway
Feature flags explained in one principle: they separate deploying code from releasing features, turning risky all-or-nothing launches into controlled, reversible switches you flip at runtime. Start simple—wrap your next risky change in a single flag, deploy it off, and turn it on gradually—but commit from day one to removing release flags once they've served their purpose. Used with discipline, feature flags make shipping safer and faster; left to pile up, they become debt. The difference is entirely in the cleanup.