A feature flag (also called a feature toggle, feature switch or feature bit) is a conditional check in your code whose value is decided at runtime, so you can turn a feature on or off for some or all users without redeploying. The code for both paths ships to production; a separate piece of configuration decides which path each request takes.
That decouples two things that used to be inseparable: shipping code to production and releasing functionality to users. Once you can deploy a feature in the off
state and flip it live later, the entire risk profile of a release changes.
Below: what feature flags actually are, how flag evaluation works under the hood, the five canonical patterns you'll use them for, how flags relate to Git branches (including Bitbucket, GitHub and GitLab), the implementation tradeoffs that bite teams in year two, how AI coding agents and flag-platform MCP servers change the way you manage flags, and how they fit into a modern deployment workflow.
The core idea: separate deploy from release
In a traditional release, a deploy is the release — the moment your code reaches production, users see the new behaviour. That coupling forces a choice: ship slow and safe with long-lived branches, or ship fast and accept that any bad commit is immediately user-facing.
Feature flags break that coupling. Code can ship to production every day, fully merged to main, while still hidden behind an if (flags.newCheckout) { ... } check. The deploy is just infrastructure plumbing. The release is a separate, deliberate act — flipping a config value, often without touching the codebase at all.
This is what makes feature flags a foundational pattern for trunk-based development and short-lived branches. When unfinished work can be merged safely behind a flag, you stop needing weeks-long feature branches that drift out of sync with main.
How feature flags work
Every feature flag system, from a ten-line helper to a commercial platform, has the same four moving parts. Pete Hodgson's widely cited feature toggles article on martinfowler.com gives them names worth borrowing:
| Part | What it is | Example |
|---|---|---|
| Toggle point | The if statement in your code where behaviour branches |
if ($flags->isOn('new_checkout', $user)) |
| Toggle router | The function or SDK that decides on or off for this request | getFlag(), or a vendor SDK client |
| Toggle configuration | The rules the router reads: on, off, a percentage, or a list of users | A config table, a YAML file, a flag service |
| Toggle context | Facts about the current request the rules can match on | User ID, plan, country, app version |
A request hits the toggle point, the router reads the configuration, matches it against the context, and returns a value. Two design decisions matter more than anything else:
- Where evaluation happens. Server-side SDKs usually download the full rule set and evaluate in memory, so a flag check costs microseconds rather than a network round trip. Browser and mobile SDKs usually do the opposite: they ask a service for the already-evaluated values for one user, so your targeting rules (and the names of unreleased features) aren't shipped to every client.
- What happens when the router can't answer. Every flag check needs a safe default that is used if the flag service is unreachable or the flag has been deleted. For a release toggle that default is almost always
off
. Choosing it deliberately is the difference between a flag outage that's invisible and one that switches half-built features on for everyone.
A minimal implementation
The simplest possible feature flag is a boolean read from configuration:
<?php
// Read flag state from environment, database, or a flag service
$isNewCheckoutEnabled = getFlag('new_checkout', $userId);
if ($isNewCheckoutEnabled) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}
Three things to notice:
- The flag is read at request time, not at boot. That lets you flip behaviour without restarting the application.
- The check accepts context (here,
$userId). The same flag can betruefor one user andfalsefor another — that's how you do percentage rollouts and targeted releases. - Both code paths are present in the binary. The old behaviour isn't deleted; it's still selectable. That's what makes the rollback instant.
In a real codebase the getFlag() call usually delegates to a centralised evaluator: an in-house library backed by a config table, an open-source flag server you host yourself, or a managed service. We'll cover the build vs buy tradeoff later.
The same check, vendor-neutral
If you want to avoid hard-wiring one vendor's SDK into hundreds of toggle points, OpenFeature is worth knowing about. It's a CNCF incubating project that defines a standard flag evaluation API; you write your toggle points against OpenFeature and plug in a provider
for whichever backend you use. In Node.js it looks like this:
import { OpenFeature } from '@openfeature/server-sdk';
// Once at startup: register the provider for your flag backend
// await OpenFeature.setProviderAndWait(new YourFlagProvider());
const client = OpenFeature.getClient();
const showNewCheckout = await client.getBooleanValue(
'new-checkout', // flag key
false, // safe default if evaluation fails
{ targetingKey: user.id, plan: user.plan } // evaluation context
);
The flag key, the default and the context map directly onto the toggle point, safe default and toggle context described above. Swapping backends later means changing the provider, not every if statement.
Five canonical use cases
Feature flags get used for a lot of things, but in practice almost every flag falls into one of five categories. Naming them matters — the same boolean variable means very different things depending on which job it's doing, and conflating them creates flag debt.
Hodgson's article groups flags into four categories (release, experiment, ops and permissioning toggles). We split gradual rollouts out as a fifth because, in deployment work, they're the pattern teams reach for most often.
1. Release toggles (the off switch
for unfinished work)
The most common pattern: a flag that hides in-flight work so it can be merged to main without being visible to users. The flag exists for the duration of the development cycle, gets turned on when the feature ships, and is then deleted from the code within a sprint or two. These are short-lived by design.
2. Kill switches (operational toggles)
A flag wrapped around a feature you suspect might break — payment retries, a third-party integration, an expensive query path. When something goes wrong in production at 2am, you flip the kill switch instead of rolling back a deploy. Kill switches are usually long-lived; they exist for the lifetime of the risky subsystem.
3. Canary releases and gradual rollouts
Instead of a binary on/off, the flag evaluates to true for a percentage of traffic — 1%, then 5%, then 25%, then 100%. Each step gives you real production data before the blast radius widens. This is the same idea as canary deployments at the infrastructure level, but pushed down into the application layer so it can be controlled per-feature instead of per-deploy.
One detail that catches people out: percentage rollouts must be sticky. Good flag systems hash a stable identifier (usually the user ID plus the flag key) into a bucket, so the same user gets the same answer on every request. If you roll a random number per request instead, a user at 10%
sees the new checkout on one page load and the old one on the next.
4. A/B experiments
The flag becomes part of an experiment: bucket users into variants, measure a metric (conversion, time-on-page, error rate), and decide which variant wins. Experiments are time-bounded — once the result is in, the losing variant gets deleted.
5. Entitlement / permission flags
The flag encodes who can use a feature: paid plans, beta cohorts, internal staff, specific regions. These are long-lived business logic, not deployment artefacts. They tend to outlive everything else and are often best moved out of your flag system into an explicit entitlements service once the count gets high.
The trap: treating all five the same. Release toggles and entitlement flags have opposite lifecycles, opposite ownership, and opposite testing requirements. Mixing them in one bucket is how teams end up with thousands of stale flags nobody can safely delete.
Feature flags vs feature branches in Git
Feature branches and feature flags both answer the question how do I keep unfinished work away from users?
, but they do it in different places:
- A feature branch isolates unfinished work in version control. The code isn't on
main, so it can't reach production. The cost is integration: the longer the branch lives, the bigger and riskier the eventual merge. - A feature flag isolates unfinished work at runtime. The code is on
mainand deployed, but switched off. The cost is the extra conditional in your code, and the discipline to remove it later.
They aren't mutually exclusive. Most teams that use flags still open short-lived branches for code review; the flag simply means the branch can merge after a day or two instead of waiting for the whole feature to be finished. If you're choosing a branching model with that in mind, our overview of Git branching strategies from GitHub Flow to trunk-based covers which models lean on flags and which avoid them.
A quick way to tell the related terms apart:
| Term | Changes at runtime? | Per-user? | Lives in |
|---|---|---|---|
| Feature branch | No | No | Git |
| Environment variable / config setting | Usually needs a restart or redeploy | No | Server or deploy config |
| Feature flag | Yes | Yes | Flag configuration, evaluated in app code |
Feature flags in Bitbucket, GitHub and GitLab
Because flags are evaluated by your running application, the Git host matters less than people expect. A flag is ordinary code in your repository plus some configuration somewhere else. What differs is how much the platform helps with the somewhere else
:
- Bitbucket has no built-in feature flag system that we could find in Atlassian's documentation. Flags live in your application code and are managed by a library or flag service you choose. On the Atlassian side, Jira has integrations with flag providers (Atlassian names LaunchDarkly, Split and Harness) that show a flag's rollout status on the related issue, which is useful for tracing a ticket from commit to release.
- GitLab ships built-in feature flags on all tiers, including Free. They expose an Unleash-compatible API, so your application reads them with a standard Unleash client library.
- GitHub has no feature flag product for repositories that we are aware of, so teams pair it with a library or flag service in the same way as Bitbucket.
Whichever host you use, the deployment side looks the same: merge the flagged code, let your pipeline ship it with the flag off, and release by changing configuration. If you're comparing what each platform's built-in CI gives you before you get to that point, see our breakdown of GitHub Actions vs GitLab CI vs Bitbucket Pipelines. For Bitbucket teams specifically, DeployHQ can deploy from Bitbucket to your servers automatically on every push to main, which is exactly the cadence flags are designed to make safe.
Already running CI/CD? DeployHQ deploys flagged code from your repo to production in the off state, so you can ship to main daily and release on your own schedule. Start a free 10-day trial, no credit card needed.
The implementation tradeoffs nobody warns you about
The first 10 flags are easy. The problems start later.
Flag debt
Every release toggle is meant to be temporary. In practice, the feature ships, the team moves on, and the if (flags.newCheckout) check stays in the code forever: both branches maintained, both tested, both adding complexity. Years in, a mature codebase can carry hundreds of dead flags that everyone is afraid to remove because no one is sure if the off
path is still reachable.
The most expensive documented example is Knight Capital in 2012. According to the SEC's order on the incident, Knight's new trading code repurposed a flag that had previously activated Power Peg
, functionality it had stopped using in 2003 but never removed. During a staged manual deployment, a technician didn't copy the new code to one of eight servers. When the flag was switched on, that eighth server ran the old Power Peg code instead. Over roughly 45 minutes Knight built up positions it never intended to hold and ultimately lost more than $460 million.
Three rules fall straight out of that story:
- Never reuse a flag name. A retired flag's key should be dead forever, not recycled for the next feature.
- Delete the code behind retired flags, not just the flag. Dead code that is
present and callable
is a liability waiting for a trigger. - Make deployments consistent across every server. Flags assume every instance runs the same build. Automated, all-or-nothing releases, like the atomic and zero-downtime deployment strategies we've covered before, remove the
one server didn't get the update
failure mode entirely.
The day-to-day mitigation is process, not tooling: every release toggle gets an owner and an expiry date when it's created, and removing it is part of the definition of done for the feature it gated.
Evaluation latency
Reading a flag has to be fast. If the lookup hits a remote service on every request, you've added a network call to the hot path of every page load. Production flag systems solve this with in-memory evaluation — the SDK streams flag rules to the client, evaluates locally, and only reports usage back asynchronously. If you're building your own, this is the part that's harder than it looks. Stale-but-fast almost always beats fresh-but-slow.
Configuration drift
Flag state usually differs across environments — on in staging, off in production, 50% rollout in canary. Without a single source of truth, a developer fixes a bug locally that they can't reproduce in staging because the flag is in a different state. Treat flag configuration the same way you treat infrastructure: version-controlled where possible, with an audit log of who flipped what and when.
Testing the matrix
Every flag doubles the number of code paths in theory. In practice, you can't test every combination of N flags; that's 2N states. The discipline is to test each flag's on and off paths in isolation, and to keep flag interactions explicit (one flag shouldn't silently depend on another). Long-lived flags need both paths covered by tests; otherwise the off
branch quietly rots. If your pipeline already runs application tests during each deployment, add a run with your long-lived flags forced to their non-default values.
Missing observability
A flag that nobody is watching is worse than no flag at all. You need to know: which flags are evaluated, how often, by which users, and what the outcomes are. When a kill switch saves you at 2am, the value isn't the boolean, it's the metric that told you to flip it. Wire flag evaluations into your existing logging and metrics pipeline from day one, alongside the deployment monitoring signals you already track, so you can tell a bad deploy apart from a bad flag change.
Build vs buy: when feature flags as a service makes sense
Build it yourself when your needs are small, your team has spare engineering capacity, and you don't need user targeting or experimentation. A boolean column in a config table plus a thin wrapper gets you most of the value. A self-hosted open-source flag server is the middle ground if you want a management UI without a vendor contract.
The same logic applies to picking between continuous delivery and continuous deployment: start with what your release cadence actually needs, not the most advanced setup you can imagine.
Buy a managed service (feature flags as a service) when you need percentage rollouts, user-attribute targeting, experiment analytics, audit logs for compliance, or SDKs across multiple languages. Writing your toggle points against OpenFeature keeps that decision reversible.
The common mistake is buying too early, spending months integrating a platform before you have enough flags to justify it, or buying too late and trying to migrate hundreds of homegrown flags into a vendor format under deadline pressure.
Where feature flags fit in a deployment workflow
Feature flags don't replace deployment automation; they sit on top of it. A modern release pipeline looks like:
- Code merges to
mainbehind a flag (flag = off). - CI runs and the automated deployment pipeline ships the build to production.
- The flag stays off for everyone except internal testers.
- QA and product validate in production with real data.
- The flag rolls out gradually — 1% → 10% → 50% → 100%.
- The flag is removed from the code once the feature is stable.
This is the workflow that continuous deployment was always aiming for: every commit to main is production-ready, deployments happen many times a day, and the business decision about when users see something is decoupled from the engineering decision about when code ships.
Flags don't replace rollbacks
A flag reverts behaviour; a rollback reverts code. Flipping a kill switch is the fastest fix when the problem is inside the flagged path. It does nothing for a bug outside the flag, a broken dependency upgrade, or a database migration that has already run. Keep both tools sharp: flags for the feature you're nervous about, and a tested process for rolling back a deployment for everything else. For migrations in particular, use the expand-and-contract pattern (add the new column, ship code that works with both schemas, remove the old column later) so that either flag state works against the current schema.
If you're still working out which pieces of that pipeline you have and which you're missing, our complete guide to CI/CD pipelines covers the deployment automation side end-to-end. Once that's in place, layering feature flags on top is a much smaller job.
Want an undo button for the code as well as the feature? DeployHQ gives every deployment a one-click rollback to the previous release, so a bad deploy is a button press away from fixed even when no flag covers it.
Managing feature flags with AI coding agents
AI coding assistants change flag work in three ways: they write more of the code that goes behind flags, they can now operate flag platforms directly, and they are starting to do the dull job of removing dead flags.
Flags are a good fit for AI-generated code
An agent can produce a working feature quickly, but working
and ready for every user
are different things. A flag lets you merge and deploy that code with the switch off, review it in production with internal users, and turn it on gradually. If it misbehaves, you flip the flag instead of rolling back a deploy. Pair this with the review habits in our guide to committing AI-generated code: the flag limits the blast radius, and review catches what the agent got wrong.
Flag platforms now ship MCP servers
The Model Context Protocol (MCP) lets an assistant call a platform's tools directly. Several flag vendors now offer an official server, so you can ask an assistant to create a flag, check its state or change a rollout without leaving your editor:
- LaunchDarkly has a hosted MCP server that signs in with OAuth and works with clients such as Cursor, Claude Code, VS Code with Copilot and Windsurf. Its docs say the hosted server is not available in its federal or EU environments.
- Flagsmith has an official MCP server for its Admin API, hosted for its SaaS and runnable as a container if you self-host.
- Statsig has an MCP server where the key you supply decides whether the assistant can only read or can also write.
- Unleash has an MCP server that can create flags, check for existing ones and generate the guard code around a change. Its docs recommend turning on change requests for production environments.
These tools change quickly, so check each vendor's documentation for what is available today.
Cleaning up stale flags with an agent
Flag debt is the problem AI is most useful for, because removing a flag means finding every call site and simplifying the logic around it. Uber describes an agentic system, AutoClean, that detects stale flags, produces tested changes and sends them to engineers for review. Two of its lessons apply to any team:
- Keep deterministic tools in the loop. A small mistake when simplifying a condition can change which branch runs in production, so Uber has the agent call a code transformation engine instead of rewriting conditions freely.
- A green build is not enough. Uber reports an agent that
fixed
a failing test by deleting it, even though the test covered behaviour that should have stayed. Review the diff, not just the CI result.
Guardrails for agents that touch flags
A flag change is a production change. A change made through an MCP server does not appear in your Git history, so it skips the code review you rely on elsewhere. Set the rules before you connect an agent:
- Start read-only. Give the assistant a key that can list and inspect flags, and add write access only when you have a reason.
- Keep production flips with a person. Let the agent propose a rollout change; have a human approve changes to production flags.
- Use a separate key per agent and environment, so one assistant cannot reach flags it has no business with.
- Rely on the audit log. Make sure your flag platform records who or what changed a flag, and when.
- Send cleanups through pull requests. An agent that removes a flag should open a normal PR, reviewed like any other change.
Where deployment fits
Flags decide who sees a feature. Deployment decides which code is running. An agent can help with both, but they are separate controls. DeployHQ's hosted MCP server lets an assistant answer questions about your projects, servers and deployments; at the time of writing (30 October 2026) it is read-only, so use the CLI for anything that changes a deployment. Our comparison of CLIs and MCP for coding agents explains how to choose between them.
A practical starting point
If you've never used feature flags before, don't start by buying a platform. Start by wrapping a single risky feature in a boolean flag read from your existing configuration system. Ship it off, validate it in production, flip it on. Then do it again with the next risky feature.
After a handful of flags, you'll know whether you need a managed platform, and you'll know which capabilities (percentage rollouts? targeting? experiments?) actually matter for your team, instead of paying for ones you don't.
The goal isn't to use feature flags everywhere. The goal is to make the riskiest changes invisible until you're confident they shouldn't be — and to give yourself an instant off switch when you're wrong.
Feature flag FAQ
Is a feature flag the same as a feature toggle?
Yes. Feature flag, feature toggle, feature switch and feature bit all describe the same technique. Toggle
is common in older literature such as Hodgson's article; flag
is more common in tooling today.
Is an environment variable a feature flag? It can act as a very simple one, but it's usually read at boot and applies to every user, so changing it needs a restart or redeploy. A feature flag is evaluated per request and can return different values for different users.
When should I remove a feature flag? Release toggles and experiment flags should be removed as soon as the feature is fully rolled out or the experiment has a result. Kill switches and permission flags can be long-lived, but they still need an owner and a periodic review.
Questions about deploying flagged code safely? Email us at support@deployhq.com or follow @deployhq on X.