Modern software teams are expected to release new features quickly without sacrificing reliability. However, deploying a large change directly to every user can introduce significant risk. A defect, performance problem, or unexpected user reaction may require an emergency rollback.
Feature flags provide a safer alternative.
A feature flag allows developers to deploy code while controlling whether the new functionality is active. Instead of tying the deployment of code directly to the release of a feature, teams can deploy the code first and enable the feature later for selected users, environments, or percentages of traffic.
This approach has become an important part of continuous integration, continuous delivery, experimentation, and modern DevOps practices.

What Is a Feature Flag?
A feature flag, also called a feature toggle, feature switch, or feature flipper, is a software development technique that allows an application to change its behavior without requiring a new code deployment.
At its simplest, a feature flag is a conditional decision:
if (featureFlags.isEnabled("new-checkout")) { return newCheckoutService.processOrder(order);}return existingCheckoutService.processOrder(order);
When the new-checkout flag is enabled, the application uses the new checkout process. When it is disabled, the application continues using the existing implementation.
The flag value may come from:
- An application configuration file
- An environment variable
- A database
- A centralized configuration service
- A feature flag management platform
- A vendor-neutral feature flag provider
- A custom internal service
Feature flags can be simple Boolean values, but modern implementations may also return strings, numbers, or structured values.
For example, a flag might determine:
checkout-experience = "version-b"search-result-limit = 50recommendation-model = "model-2026-07"
The main idea is that deploying code and releasing functionality become separate activities.
The History and Roots of Feature Flags
Feature flags are often described as a modern DevOps technique, but their underlying concept is much older.
Software developers have used configuration settings, command-line switches, conditional compilation, environment variables, and runtime options for decades. Feature flags adapted these familiar mechanisms to support frequent integration and controlled software delivery. Continuous Delivery describes feature toggles as a newer name for the long-established configuration-option pattern.
The technique became especially important as development teams moved away from long-lived feature branches and toward continuous integration.
In a traditional feature-branch workflow, developers might work on a new feature for several weeks or months before merging it into the main branch. As the branch becomes increasingly different from the main codebase, merging becomes more difficult and risky.
Feature flags offered another option:
- Developers integrate incomplete code into the main branch.
- The unfinished functionality remains disabled.
- The main branch stays deployable.
- The feature is activated only when it is ready.
Feature flags received significant attention during the growth of DevOps and continuous deployment. Flickr’s influential 2009 presentation, “10+ Deploys per Day,” included feature flags among the practices that helped the company deploy frequently while controlling feature exposure.
In 2010, Martin Fowler documented feature flags as a practical way to keep teams working on the mainline while preventing unfinished features from appearing in a release.
Pete Hodgson later developed a more detailed model that categorized flags as release, experiment, operations, and permissioning toggles. His work also emphasized that flags introduce complexity and must be actively managed and removed when they are no longer necessary.
More recently, projects such as OpenFeature have introduced vendor-neutral APIs for evaluating feature flags. This allows applications to use a standard programming interface while changing the underlying flag provider with less application-level refactoring.
Why Do We Need Feature Flags?
Feature flags exist because deployment and release are not necessarily the same event.
Deployment
Deployment is a technical activity. It moves a new version of the application into an environment such as testing, staging, or production.
Release
Release is a product or business activity. It makes functionality available to users.
Without feature flags, these events often happen at the same time:
Deploy code → Feature becomes available to everyone
With feature flags, they can be separated:
Deploy code ↓Keep feature disabled ↓Enable for internal users ↓Enable for 5% of customers ↓Monitor results ↓Gradually increase access ↓Release to everyone
Continuous Delivery identifies decoupling deployment from release as one of the principles of low-risk software delivery. It allows teams to deploy continuously while choosing when and how new functionality becomes available.
This separation gives engineering, operations, and product teams greater control over the release process.
Why Are Feature Flags Important?
Feature flags address several common software delivery problems.
Reducing Release Risk
A new feature does not need to be enabled for every user immediately. Teams can begin with a small group and monitor the results before expanding the rollout.
Supporting Continuous Integration
Developers can merge code into the main branch more frequently instead of maintaining long-running branches.
Enabling Fast Recovery
When a flagged feature causes problems, the team may be able to disable it immediately without rebuilding and redeploying the application.
Supporting Product Experiments
Different user groups can receive different experiences, allowing teams to compare business and usability outcomes.
Coordinating Business Launches
Code can be deployed before a marketing campaign, contractual launch date, regulatory approval, or customer announcement. The feature can then be activated at the appropriate time.
Limiting the Blast Radius
A problem affecting 2% of users is usually easier to manage than a problem affecting the entire customer base.
How Do Feature Flags Work?
A feature flag system normally contains several components.
1. Flag Definition
Every flag needs a unique key.
new-checkout-flowenable-ai-recommendationsuse-new-pricing-engineemergency-disable-file-upload
The flag should also include useful metadata, such as:
- Description
- Owner
- Flag type
- Default value
- Creation date
- Expiration date
- Related work item
- Environments
- Expected removal plan
2. Flag Configuration
The configuration defines the current value and any targeting rules.
A simple configuration might look like this:
{ "new-checkout-flow": false, "enable-ai-recommendations": true}
A more advanced configuration might include rollout rules:
{ "new-checkout-flow": { "enabled": true, "percentage": 10, "allowedRegions": ["US", "CA"], "allowedPlans": ["premium"] }}
3. Evaluation Context
The application may use contextual information when evaluating a flag.
Examples include:
- User ID
- Account ID
- Subscription level
- Geographic region
- Application version
- Device type
- Employee status
- Environment
- Organization
- Request properties
OpenFeature refers to this information as the evaluation context. It can be used for rule-based targeting, individual overrides, and percentage-based distribution.
An evaluation request might look like this:
EvaluationContext context = EvaluationContext.builder() .targetingKey(user.getId()) .set("region", user.getRegion()) .set("plan", user.getSubscriptionPlan()) .build();boolean enabled = featureFlagClient.getBooleanValue( "new-checkout-flow", false, context);
4. Flag Evaluation
The feature flag client evaluates the flag using:
- The flag key
- The default value
- The current configuration
- The evaluation context
- Targeting and percentage rules
The result determines which behavior the application should use.
5. Decision Point
A decision point is the location in the application where the flag result affects the behavior.
CheckoutProcessor processor = featureFlags.isEnabled("new-checkout-flow", user) ? newCheckoutProcessor : existingCheckoutProcessor;return processor.process(order);
Decision points should be limited and intentional. Scattering flag checks throughout the codebase makes flags difficult to test and remove.
6. Monitoring and Feedback
The application should record enough information to understand the effect of a rollout.
Useful measurements include:
- Error rate
- Request latency
- CPU and memory usage
- Conversion rate
- Abandonment rate
- User engagement
- Support tickets
- Business transaction failures
- Flag evaluation failures
Technical and business metrics should be evaluated together. A feature may be technically stable while still producing poor business results.
Main Types of Feature Flags
Not every flag serves the same purpose. Classifying flags helps teams determine how they should be configured, tested, secured, and removed.
1. Release Flags
Release flags hide unfinished or unreleased functionality.
Developers can merge the implementation into the main branch while keeping it unavailable to normal users.
Example:
if (featureFlags.isEnabled("new-customer-dashboard")) { return newDashboard();}return existingDashboard();
Release flags should normally be temporary. After the rollout is complete and the new behavior is stable, the flag and old implementation should be removed.
2. Experiment Flags
Experiment flags support A/B tests and controlled product experiments.
For example:
- Group A sees a blue registration button.
- Group B sees a green registration button.
- The product team compares registration completion rates.
Experiment flags should use stable assignment. A user should not randomly switch between experiences on every request.
3. Operations Flags
Operations flags help control system behavior during incidents, traffic spikes, or dependency failures.
Examples include:
- Disabling an expensive recommendation engine
- Turning off image processing
- Reducing background-job frequency
- Disabling a nonessential integration
- Switching to a simpler algorithm
- Preventing new file uploads temporarily
These flags are sometimes called kill switches or circuit-breaker flags.
Unlike most release flags, some operations flags may remain in the system for a long time because they are part of the operational resilience strategy.
4. Permissioning Flags
Permissioning flags control which users or organizations can access a capability.
Examples include:
- Premium subscription features
- Internal administrative tools
- Customer-specific functionality
- Beta programs
- Region-specific features
- Early-access programs
These flags may be long-lived. However, authorization-sensitive decisions should not rely solely on a client-side flag.
A user hiding or modifying a browser-side flag must never gain access to protected data or operations. The server must continue enforcing authorization.
Key Use Cases for Feature Flags
Gradual Rollouts
A feature can be released progressively:
Internal users → 1% → 5% → 25% → 50% → 100%
At each stage, the team reviews system health and business metrics before continuing.
Canary Releases
A new capability or implementation is enabled for a small portion of production traffic. If it behaves correctly, the rollout percentage increases.
Unlike a traditional infrastructure canary, a feature-level canary may exist inside the same deployed application version.
Dark Launches
A feature is deployed and may even execute in production, but users do not see its output.
For example, an application might run both an old and new search algorithm. Users continue receiving results from the old algorithm while developers compare the new algorithm’s latency and accuracy.
A/B Testing
Feature flags can route users into stable experiment groups and measure which experience performs better.
Beta and Early-Access Programs
A feature can be made available to:
- Employees
- Test accounts
- Selected customers
- Partner organizations
- Customers who opt into a beta program
Emergency Kill Switches
When a new or optional component causes failures, the team can disable it without waiting for a full redeployment.
Infrastructure and Service Migrations
Flags can route traffic between old and new implementations.
PaymentGateway gateway = featureFlags.isEnabled("use-new-payment-provider", account) ? newPaymentGateway : existingPaymentGateway;
This can support:
- Database migrations
- API replacements
- Cloud migrations
- Search-engine upgrades
- Payment-provider changes
- New caching strategies
- Machine-learning model upgrades
Regional Rollouts
Features can be introduced gradually by country, state, market, or data center. This may help with localization, capacity planning, support readiness, or regulatory requirements.
Customer-Specific Features
In business-to-business applications, selected organizations may receive customized or preview functionality without requiring separate application deployments.
Benefits of Feature Flags
Safer Production Releases
Teams can expose changes gradually instead of performing an all-at-once launch.
Faster Feedback
Production behavior can be observed using real traffic and realistic workloads.
Reduced Dependence on Rollbacks
A faulty feature can sometimes be disabled without rolling back unrelated improvements included in the same deployment.
Smaller and More Frequent Changes
Feature flags support trunk-based development and frequent integration by allowing incomplete functionality to remain inactive.
Better Collaboration
Engineering can deploy when the software is technically ready, while product and business teams can decide when the feature should be launched.
Controlled Experimentation
Product decisions can be based on measured results rather than assumptions.
Improved Operational Resilience
Operations teams can disable nonessential or problematic behavior during incidents.
Challenges and Risks
Feature flags are powerful, but they are not free.
Increased Code Complexity
Every flag may introduce another possible execution path.
Flag A: on or offFlag B: on or offFlag C: on or off
Three Boolean flags can theoretically create eight combinations. Ten flags can create 1,024 combinations.
Teams should not attempt to test every theoretical combination. They should identify supported configurations and test combinations where flags interact. Fowler and Hodgson both warn that feature flags increase validation and maintenance costs.
Stale Flags
A temporary flag can become permanent because nobody removes it after the rollout.
Stale flags create:
- Dead code
- Confusing behavior
- Additional test cases
- Unknown dependencies
- Operational uncertainty
- Increased cognitive load
Incorrect Default Values
If a flag service becomes unavailable, an unsafe fallback value can activate risky functionality or disable a critical capability.
Inconsistent User Experiences
Poorly designed percentage rollouts may assign the same user to different experiences across requests, devices, or services.
Security Problems
Client-side feature flags are visible to users and can often be modified. They must not replace server-side authentication, authorization, or entitlement checks.
Dependency Problems
Disabling the user interface does not necessarily disable background jobs, API endpoints, database writes, messages, or downstream service calls associated with the feature.
Feature Flag Best Practices
1. Give Every Flag an Owner
A person or team should be accountable for the flag’s rollout, monitoring, and removal.
2. Assign an Expiration Date
Temporary flags should include a cleanup date.
Flag: new-checkout-flowOwner: Checkout TeamCreated: July 12, 2026Expected removal: August 30, 2026
Expired flags should generate alerts or fail automated governance checks.
3. Create the Removal Task Immediately
When a release flag is created, add its removal work item to the backlog at the same time.
Do not wait until after the launch to remember that cleanup is required.
4. Use Safe Defaults
Every evaluation should provide a deliberate fallback value.
boolean enabled = featureFlags.getBoolean( "new-payment-flow", false);
The fallback should normally preserve the safest known behavior.
However, “false” is not automatically the correct default for every flag. For example, an emergency security control may need to fail closed rather than fail open.
5. Centralize Evaluation Logic
Avoid repeating complex targeting conditions throughout the application.
Poor approach:
if (user.isPremium() && user.getRegion().equals("US") && user.getAccountAgeDays() > 30) { // New behavior}
Better approach:
boolean enabled = featureFlags.isEnabled( "advanced-reporting", user);
The flag service or a dedicated policy component should own the targeting rules.
6. Minimize Decision Points
Evaluate the flag near the feature’s entry point instead of adding checks to every internal method.
For a new page, toggling the navigation link and server-side route may be enough. Every class used by that page may not need its own flag check.
7. Avoid Deeply Nested Flags
Code such as this is difficult to understand:
if (flagA) { if (flagB) { if (!flagC) { // Complex behavior } }}
When flags must interact, model the supported states explicitly or use separate strategy implementations.
8. Test Both Important States
For a release flag, automated tests should normally cover:
- Existing behavior when the flag is disabled
- New behavior when the flag is enabled
- Failure or fallback behavior when evaluation is unavailable
- Important interactions with related flags
9. Keep Rollouts Stable
Percentage-based assignment should use a stable identifier such as a user ID, account ID, or organization ID.
Conceptually:
hash(flag-key + account-id) % 100
The same account should remain in the same rollout group while the percentage changes.
10. Monitor by Flag Variation
Telemetry should identify which flag variation was active when a request was processed.
For example:
request.durationfeature.new-checkout-flow = enabledfeature.variation = version-b
This allows teams to compare errors and performance between the old and new behaviors.
Avoid placing personally identifiable information directly into logs or high-cardinality metric labels.
11. Protect Administrative Changes
Changes to production flags should use:
- Role-based access control
- Audit logs
- Change history
- Approval workflows for critical flags
- Multi-factor authentication
- Environment-level permissions
- Notifications for important changes
Changing a production flag can have the same impact as deploying code and should be governed accordingly.
12. Remove the Old Code
After a successful rollout:
- Make the new behavior permanent.
- Remove the old implementation.
- Remove the flag condition.
- Delete the flag configuration.
- Remove obsolete tests.
- Update documentation.
- Verify that dashboards and alerts no longer reference the flag.
Pete Hodgson recommends treating flags as inventory with a carrying cost and proactively limiting their number.
How to Integrate Feature Flags into Your Development Process
Feature flags should be introduced as an engineering practice, not merely as another library.
Step 1: Define a Flag Policy
Document which types of flags your organization supports.
A basic policy should answer:
- Who can create a flag?
- Who can modify production flags?
- Which metadata fields are required?
- How are flags named?
- Which flags require approval?
- How long may a release flag exist?
- What is the cleanup process?
- How are emergency flags tested?
- How are changes audited?
A naming convention might look like:
release.new-checkoutexperiment.registration-buttonops.disable-recommendationspermission.advanced-reporting
Step 2: Introduce an Application-Level Abstraction
Do not let business code depend directly on a particular vendor throughout the codebase.
Create an internal interface:
public interface FeatureFlagService { boolean isEnabled(String flagKey); boolean isEnabled(String flagKey, UserContext context); String getStringValue( String flagKey, String defaultValue, UserContext context );}
The implementation can use:
- Local configuration
- A database
- A commercial platform
- An open-source platform
- An OpenFeature provider
- A custom service
OpenFeature provides a vendor-neutral evaluation API and provider abstraction, which can reduce code-level coupling to a particular flag system.
Step 3: Start with a Low-Risk Feature
Choose a feature that:
- Is not security-critical
- Has a clear old and new behavior
- Can be monitored
- Can be disabled safely
- Has a defined owner
- Has a short expected lifetime
Avoid using the first flag for a complicated database migration or critical payment process.
Step 4: Add Tests to the CI Pipeline
The pipeline should test the supported flag states.
For example:
@Testvoid shouldUseExistingCheckoutWhenFlagIsDisabled() { when(featureFlags.isEnabled("new-checkout-flow", user)) .thenReturn(false); CheckoutResult result = checkoutService.checkout(order, user); verify(existingCheckoutProcessor).process(order); verifyNoInteractions(newCheckoutProcessor);}
@Testvoid shouldUseNewCheckoutWhenFlagIsEnabled() { when(featureFlags.isEnabled("new-checkout-flow", user)) .thenReturn(true); CheckoutResult result = checkoutService.checkout(order, user); verify(newCheckoutProcessor).process(order); verifyNoInteractions(existingCheckoutProcessor);}
Step 5: Deploy with the Flag Disabled
Deploy the new code to production while the feature remains unavailable to customers.
Confirm that:
- Existing functionality still works
- The flag can be evaluated
- The fallback behavior works
- Logs and metrics include the flag state
- The new code does not create unexpected side effects
Step 6: Enable the Feature Internally
Begin with developers, testers, product owners, or selected internal accounts.
This phase can reveal usability problems without affecting the general customer population.
Step 7: Begin a Gradual Rollout
A practical rollout might be:
Internal users ↓Selected beta customers ↓1% of eligible accounts ↓5% ↓25% ↓50% ↓100%
The exact percentages should depend on traffic volume, risk, and how quickly meaningful metrics become available.
Step 8: Define Stop Conditions
Before beginning the rollout, define what should pause or reverse it.
Examples include:
- Error rate increases by more than 1%
- P95 latency increases by more than 200 milliseconds
- Checkout completion decreases by more than 3%
- Support requests exceed a defined threshold
- Database load reaches an unsafe level
A rollout should not depend entirely on subjective judgment during an incident.
Step 9: Complete the Rollout
After the feature is enabled for all eligible users, continue monitoring it for an agreed stabilization period.
Do not assume that reaching 100% means the work is complete.
Step 10: Remove the Flag
Once the new behavior is stable, remove the old path and the release flag.
A release flag’s lifecycle should be:
Proposed ↓Created ↓Implemented ↓Deployed disabled ↓Internal testing ↓Gradual rollout ↓Fully enabled ↓Observed ↓Removed
“Fully enabled forever” should not be the final state of a temporary release flag.
Feature Flags and Database Changes
Database changes require additional care because disabling application behavior does not automatically reverse a schema or data migration.
Use backward-compatible migration patterns:
- Add the new schema without removing the old schema.
- Deploy code that can work with both versions.
- Begin writing to the new structure when appropriate.
- Backfill existing data.
- Validate the migrated data.
- Switch reads using a flag.
- Monitor the new path.
- Stop using the old structure.
- Remove the flag.
- Remove the obsolete schema in a later deployment.
Avoid making a destructive database change that assumes the feature flag will never be disabled.
When Not to Use Feature Flags
Feature flags should not be the default solution for every development problem.
Avoid or reconsider a flag when:
- The change can be delivered safely as a small vertical slice.
- The code is unlikely to be deployed before it is ready.
- The flag would remain permanently without a clear reason.
- The change requires an irreversible database operation.
- The flag is being used instead of proper authorization.
- The team cannot monitor the enabled behavior.
- The old and new implementations cannot safely coexist.
- The flag adds more complexity than the risk it reduces.
Martin Fowler recommends first considering smaller releases or a keystone interface, where a feature is built and integrated but exposed through a final simple entry point. Release flags are most useful when those simpler approaches are not practical.
Feature Flags Versus Configuration Settings
Feature flags and configuration settings may use the same technical mechanisms, but they have different purposes.
A configuration setting usually controls how the system operates:
maximum-upload-size = 25 MBconnection-timeout = 30 seconds
A feature flag usually controls which product behavior is available:
new-upload-experience = enabled
The most important difference is lifecycle.
Many configuration settings are expected to remain permanently. Most release and experiment flags should be temporary and removed after they have served their purpose.
Feature Flags Versus Feature Branches
Feature branches isolate changes in source control. Feature flags isolate behavior at runtime.
Feature branches may be appropriate for short-lived work, prototypes, or changes that should never enter the deployable main branch before completion.
Feature flags are particularly useful when teams need to:
- Integrate frequently
- Keep the main branch deployable
- Test in production
- Perform gradual rollouts
- Separate deployment from business launch
- Disable behavior dynamically
Feature flags do not eliminate the need for branches. They reduce the need for long-lived branches that delay integration.
Conclusion
Feature flags allow software teams to control the release of functionality independently from the deployment of code.
Used correctly, they support:
- Continuous integration
- Safer production releases
- Gradual rollouts
- A/B testing
- Dark launches
- Operational kill switches
- Customer-specific capabilities
- Infrastructure migrations
- Faster recovery from problems
However, every flag creates additional states, tests, ownership responsibilities, and cleanup work. An unmanaged feature flag system can become a source of technical debt and operational risk.
Successful teams treat feature flags as controlled inventory. Each flag has a defined purpose, owner, default value, rollout strategy, monitoring plan, expiration date, and removal task.
The goal is not to place a flag around every change. The goal is to use feature flags selectively so that software can be released more safely, incrementally, and confidently.
Recent Comments