← Blog

What are feature flags? A developer's guide

A feature flag is a switch in your code that turns a feature on or off without a new deploy. Feature flags let you ship code that stays dark until it is ready, roll a change out to 5% of users, run an A/B test, or turn a broken feature off fast.

The idea is old and the name is not settled. Some teams say feature toggle, feature switch, or flipper. Martin Fowler’s canonical write-up calls them feature toggles. They all describe the same move: wrap a code path in a condition, then decide at runtime whether it runs.

This guide covers what a feature flag is, how one works under the hood, the types you will actually use, and the part most tutorials skip. Flags are easy to add and hard to remove, and a flag nobody removes is the default way this goes wrong.

Key Takeaways

  • A feature flag is a runtime switch that turns a code path on or off without a deploy. It splits deploying the code from releasing the feature.
  • The four common types are release, experiment, ops (kill switch), and permission flags, following Martin Fowler’s taxonomy.
  • A flag is a check at a call site plus a rule that resolves it to a value for each user, often by hashing the user ID into a bucket.
  • The real cost is removal, not addition. A stale flag stuck at 100% for two years with a dead branch behind it is the common failure mode.
  • Flags live either in a hosted dashboard or in your repo. Where they live decides whether they drift from the code they gate.

What is a feature flag?

A feature flag is a conditional in your code that decides, at runtime, whether a feature runs. Instead of deploying new code to change behavior, you change the flag’s value and the running app takes a different path. It splits deploying code from releasing a feature, so the two no longer have to happen in the same moment.

In the simplest form, a flag is a boolean check at the point where the behavior branches:

if (flags.newCheckout) {
  return <NewCheckout />;
}
return <LegacyCheckout />;

Both paths ship to production. The flag decides which one a given request runs. You can turn newCheckout on for everyone, for 10% of traffic, or for users on mobile, without shipping again. The code is deployed; the feature is not yet released. That gap, between shipping code and turning it on, is what a feature flag gives you.

How feature flags work

A flag has two parts: the call site in your code, and the rule that resolves it to a value for a given user. The call site is the if above. The rule is where the real work happens.

The simplest rule is a static boolean: on or off for everyone. The next step up is a percentage rollout, where the flag is on for some fraction of users. To do that consistently, the flag system needs the same user to get the same answer every time, or a user would flip between the new and old checkout on every page load. So the rule is usually deterministic: hash a stable user ID, map the hash into a fixed number of buckets, and assign a variant from the bucket.

dif does this with no stored state at all. A user’s variant is the first four bytes of SHA-256(salt || user_id) taken mod 10,000, then read off the flag’s cumulative weights. Nothing is written to a database, so the assignment recomputes the same way every time and the SDK makes no network call to evaluate a flag. A per-flag salt keeps cohorts independent, so being in the 10% for one flag does not correlate with any other.

Targeting layers on top of the percentage. A flag can scope to an audience first (users in a country, on a plan, on a device), then split that audience by weight. The mechanics differ by tool, but the shape is constant: a call site, an audience rule, and a deterministic split.

The four types of feature flags

Not all flags are the same, and treating them the same is how teams get burned. Martin Fowler’s taxonomy sorts them by how long they live and how often they change. The distinction matters because a flag meant to live for two days and a flag meant to live forever want different care.

  • Release flags hide unfinished work so you can merge to main before a feature is done. They support trunk-based development: ship code dark, keep pull requests small, release when ready. These are short-lived and should be deleted once the feature is fully out.
  • Experiment flags split traffic to measure a change. This is the A/B test: hold two variants at a 50/50 split, watch a primary metric, and keep the winner. dif treats these as the same file as a flag, just held at a split until the numbers answer a hypothesis. See experimentation as files for how that works.
  • Ops flags are kill switches. They let an operator turn an expensive or risky path off under load without a deploy. A payment provider degrading? Flip to the backup. These can live a long time on purpose.
  • Permission flags gate a feature to a set of users: beta testers, a paid tier, an internal team. These are long-lived by design, because they encode a product rule, not a temporary rollout.

The mistake is leaving a release flag in the code as if it were a permission flag. It served its purpose at 100%, nobody deleted it, and now it is dead config.

What feature flags are used for

The types above map to concrete jobs. In practice, teams reach for feature flags to:

  1. Ship dark and decouple deploy from release. Merge unfinished code behind an off flag, then release on a schedule that is not tied to a deploy window.
  2. Roll out gradually. Move a change from 1% to 10% to 50% to 100%, watching error rates at each step. This is a staged rollout, also called a canary release.
  3. Run A/B tests. Hold a split, measure the effect on a metric, and conclude with a decision instead of a hunch.
  4. Kill a bad change. Turn a feature off the moment it misbehaves, without waiting for a rollback deploy to build.
  5. Gate access. Ship a beta to a named list, or a premium feature to paying customers.

The common thread: a flag lets you change what users experience without changing what is deployed. That is useful, and it is also exactly what makes flags accumulate.

Feature flags vs feature toggles

There is no real difference. “Feature flag” and “feature toggle” are two names for the same thing: a runtime switch around a code path. “Toggle” leans toward on/off framing, and “flag” is common when a switch also carries targeting or a percentage, but the terms are used interchangeably across the industry. Fowler’s article standardized “feature toggle”; most product tools brand themselves around “feature flag.” Pick one name and use it consistently in your codebase so search and code review stay clean.

The hard part: removing flags

Adding a flag takes a minute. Removing one is the job nobody schedules, and it is where feature flag management actually lives.

Every flag is a branch in your code. Two boolean flags in the same function are four code paths, only one of which real users see. Left alone, release flags pile up. Six months later a directory holds thirty flags, most stuck at 100%, and no one remembers which are safe to delete. The dead branch behind each one still ships, still needs to compile, and still shows up when someone greps the code. This is the flag graveyard, and it is the single most common complaint about feature flags in production.

A few practices keep it from happening:

  • Give every flag an owner and an expected lifespan. A release flag with no removal date is a leak.
  • Keep the number of live flags small. Fewer flags, fewer combinations, fewer ways to write a bug.
  • Make flag changes reviewable. A flag flip is a behavior change; it deserves the same review as code.
  • Fail safe. When a flag can’t be resolved, default to the safe path (usually off), so a misconfiguration never fails worse than not shipping the feature.
  • Delete on conclusion. When a flag has done its job, remove the flag and the losing branch in the same change.

The friction is that in most tools the flag and the code live in two different places, so removing a flag means reconciling a dashboard against the repo by hand. Skip that chore a few times and you have a graveyard.

Where feature flags live: dashboard or repo

Most feature flag tools keep flags in a hosted dashboard. Your app calls a service (or a cached copy of it) to ask what a flag’s value is. The dashboard is convenient: flip a flag in the UI and it takes effect in under a second, no deploy. For an ops flag on a critical path, killing a bad path in one second without waiting for a build is hard to give up.

Convenience like that comes with drift. The flag lives in one system and the code it gates lives in another, so the two fall out of sync. The dashboard does not know the branch was deleted; the repo does not know the flag was turned off. A coding agent working in your repo cannot see the dashboard at all, so it will happily delete a branch behind a flag that is still live.

The other model is to keep flags in the repo. dif puts each flag and each experiment in a single Markdown file, next to the code it changes:

---
id: new-checkout
status: active
owner: sam@acme.com
surface: checkout
audience:
  include:
    - device_type: [mobile, tablet]
variants:
  - id: "off"
    weight: 90
  - id: "on"
    weight: 10
metrics:
  primary: completed_checkout
---

## Brief

Ramp to 25% once the guardrails hold for a week.

You review that file in a pull request like any other change. Its history is the git history. dif validate runs in CI and fails the build if the weights do not total 100, the owner is not a real email, or the code points at a flag that no longer exists, so the graveyard cannot accumulate silently. The tradeoff is the same one every git-native flag makes: turning a flag off is a commit and a deploy, not a sub-second dashboard toggle. For most flags that is fine; for a payment path that needs an instant kill, a streaming dashboard still wins. The open-source LaunchDarkly alternative post covers that tradeoff in full.

Getting started with feature flags

You do not need a platform to start. A configuration file with a few booleans and an if at the call site is a working feature flag system, and for a small app it may be all you need. You outgrow it when you want percentage rollouts, targeting, A/B tests, an audit trail, and something that stops old flags from rotting.

At that point the question is where the flags should live. If you want them in a dashboard with an instant kill switch, a hosted tool fits. If you want them reviewed in the same pull request as the code, versioned in git, and readable by your coding agent, keep them in the repo. dif does the second. Install the CLI with npm install -g @dif.sh/cli, and dif init scaffolds a dif/ directory, a typed client, and the flag files, no account or API key required. The feature flags page shows the full workflow, and the docs cover install.

None of this is exotic, but the discipline is the part teams skip. Add a flag on purpose, give it an owner and a removal date, and take it out once it has done its job. Handled that way, feature flags make releases safer. Ignore it and you are back to grepping through thirty flags nobody will claim, which is where this post started.

FAQ

What is a feature flag in simple terms? It is an on/off switch for a feature, controlled at runtime instead of by a deploy. You ship the code for both the old and new behavior, and the flag decides which one each user sees. Change the flag, change the behavior, no new release required.

What is the difference between a feature flag and a feature toggle? None that matters. They are two names for the same runtime switch. “Feature toggle” is the term Martin Fowler’s article popularized; “feature flag” is what most tools brand themselves around. Use one name consistently in your codebase.

Are feature flags a good idea? Yes, used with discipline. They let you decouple deploy from release, roll out gradually, and kill bad changes fast. The downside is maintenance: flags accumulate as dead code if nobody removes them, so give every flag an owner and a removal date.

How do you manage feature flags? Keep the number of live flags small, give each one an owner and an expected lifespan, review flag changes like code, fail safe when a flag can’t resolve, and delete a flag and its losing branch together once it has served its purpose. Tooling that runs in CI helps enforce this.

Do feature flags slow down your app? They add a conditional, which is negligible. The cost depends on how the flag resolves. A tool that calls a network service to evaluate a flag adds latency; one that computes assignment locally, like dif’s deterministic bucketing, adds none.

How do you remove an old feature flag? Delete the flag definition, remove the if at the call site, and delete the branch that lost, all in one change so the code and the flag state stay in sync. When flags live in the repo, this is a normal pull request that CI can verify.