Loading learning content...
Imagine this scenario: Your team has been building a critical new payment processing feature for three months. The code is tested, reviewed, and ready. But releasing it means deploying to millions of users simultaneously—with no ability to undo if something goes wrong without a full rollback, potential data corruption, and guaranteed late-night incident response.
This is the fundamental tension of software delivery: we want to deploy frequently (to reduce risk and deliver value), but we're afraid of releases (because each one is all-or-nothing). Feature flags resolve this tension by decoupling deployment from release.
With feature flags, that payment feature could be deployed to production, completely invisible to users, then gradually enabled for 1% of traffic, then 10%, then 100%—with instant kill-switch capability at every stage. The code ships continuously; the exposure is controlled dynamically.
By the end of this page, you will understand what feature flags are, the different types and their use cases, the architectural principles behind flag systems, and the benefits and risks that come with this powerful technique. You'll see why feature flags have become a cornerstone of continuous delivery and operational excellence.
A feature flag (also called feature toggle, feature switch, or feature flipper) is a software development technique that allows you to modify system behavior without changing code. At its simplest, a feature flag is a conditional statement that determines whether a particular code path is executed:
if (featureFlags.isEnabled("new-checkout-flow")) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}
But this simple conditional belies the sophistication of modern feature flag systems. In production, feature flags:
Think of feature flags as dynamic configuration that controls code paths. Unlike static configuration (environment variables, config files), feature flags are designed to change frequently, target specific users or contexts, and be toggleable without deployment. This makes them the perfect tool for separating the mechanics of shipping code from the decision of exposing features.
A full-featured flag system includes several components:
| Component | Description |
|---|---|
| Flag Key | Unique identifier for the flag (e.g., checkout-v2, payment-refactor) |
| Flag Type | Boolean, string, number, or JSON (determines what values the flag can return) |
| Default Value | What the flag returns if evaluation fails or context is missing |
| Targeting Rules | Conditions that determine which users/contexts get which values |
| Fallback Behavior | What happens when the flag system is unavailable |
| Metadata | Description, owner, creation date, expiration policy |
The evaluation context is equally important—it's the data passed to the flag system to make targeting decisions: user ID, email domain, geographic region, device type, application version, session attributes, and more.
Not all feature flags serve the same purpose, and understanding the taxonomy is critical for proper governance. The canonical classification, popularized by Pete Hodgson, identifies four distinct categories:
Release flags enable trunk-based development by allowing incomplete features to be merged and deployed without exposing them to users. They're the most common type.
Characteristics:
Example: A new dashboard widget is being developed. The code is merged behind a release flag, deployed, tested in production by internal users, then gradually rolled out. Once at 100%, the flag is removed.
Experiment flags (A/B test flags) enable controlled experiments by randomly assigning users to cohorts and measuring outcomes.
Characteristics:
Example: Testing three different checkout button colors (variant A: blue, B: green, C: orange) to measure which produces the highest conversion rate.
Ops flags (operational flags) control system behavior for reliability and performance reasons. They're circuit breakers, degradation switches, and capacity controls.
Characteristics:
Example: A flag that disables non-critical background jobs during traffic spikes, or a kill-switch that falls back to cached data when the recommendation service is degraded.
Permission flags control access to features based on user attributes, subscription level, or entitlements. They're essentially feature gating.
Characteristics:
Example: Premium users get access to advanced analytics dashboard; enterprise customers get SSO integration; beta program members get early access to experimental features.
| Type | Lifespan | Primary Owner | Typical Use Case |
|---|---|---|---|
| Release Flag | Days to weeks | Engineering | Progressive rollouts, trunk-based development |
| Experiment Flag | Weeks | Product/Growth | A/B testing, conversion optimization |
| Ops Flag | Permanent | SRE/Platform | Circuit breakers, graceful degradation |
| Permission Flag | Permanent | Product/Business | Feature entitlements, subscription tiers |
Understanding the architecture of feature flag systems reveals why they're both powerful and potentially risky. A production-grade feature flag system has three core components:
The source of truth for flag definitions, targeting rules, and values. This can be:
The store must support:
The library integrated into your application that evaluates flags. The SDK:
Critical design consideration: SDK evaluation happens in your application process, on the hot path. A slow or unreliable SDK directly impacts application latency and availability.
12345678910111213141516171819202122232425262728293031323334353637
// Conceptual SDK architectureinterface FeatureFlagSDK { // Initialize with configuration initialize(config: SDKConfig): Promise<void>; // Core evaluation method isEnabled(flagKey: string, context: EvaluationContext): boolean; // Typed value retrieval getString(flagKey: string, context: EvaluationContext, default: string): string; getNumber(flagKey: string, context: EvaluationContext, default: number): number; getJSON<T>(flagKey: string, context: EvaluationContext, default: T): T; // Lifecycle and observability shutdown(): Promise<void>; onFlagChange(flagKey: string, callback: () => void): void;} interface EvaluationContext { userId: string; email?: string; userAttributes?: Record<string, any>; sessionId?: string; deviceType?: 'mobile' | 'desktop' | 'tablet'; country?: string; appVersion?: string; // ... additional targeting attributes} interface SDKConfig { sdkKey: string; baseUrl: string; pollingIntervalMs: number; // How often to fetch updates cacheMode: 'memory' | 'persistent'; offline: boolean; // For local development analytics: boolean; // Enable usage tracking}The dashboard or API for creating, configuring, and monitoring flags. This includes:
When your code calls isEnabled("checkout-v2", context), here's what happens:
Feature flag evaluation happens on every request where flags are used. A flag system that adds 5ms of latency to every evaluation quickly becomes a major performance problem. Production-grade systems use local caching, background refresh, and streaming updates to keep evaluation under 1ms. Never make synchronous network calls during flag evaluation.
Feature flags have become essential to modern software delivery because they solve multiple problems simultaneously. Understanding these benefits helps justify the investment in flag infrastructure and governance.
Feature flags fundamentally change the psychology of deployment. When you know you can instantly disable a feature if something goes wrong, you deploy more frequently. More frequent deployments mean smaller changes, which are easier to debug. This creates a virtuous cycle: flags → confidence → frequency → smaller changes → fewer bugs → more confidence.
Feature flags are powerful, but they're not free. Organizations that adopt flags without understanding the costs often end up with more problems than they solved. Acknowledging these risks is essential for successful adoption.
A cautionary tale: A major tech company accumulated over 10,000 feature flags over five years. Teams were afraid to remove flags because they didn't know what would break. The codebase was littered with if (flag) { } else { } statements where both branches were identical. New engineers couldn't understand what the system actually did. Eventually, they had to create a dedicated team just to clean up flags. Prevention is far cheaper than cure.
The solution isn't to avoid feature flags—it's to use them with discipline:
We'll cover these governance practices in detail in the Flag Lifecycle Management page.
Feature flags are ubiquitous in modern software organizations. Here's how leading companies use them:
| Company | Scale | Notable Practice |
|---|---|---|
| Facebook/Meta | ~~100M+ flags evaluated/second | Gatekeeper system; every feature ships behind a flag |
| Netflix | High-scale experimentation | A/B testing at massive scale; UI personalization |
| Extensive internal tooling | Flags integrated into build system; experiment framework | |
| GitHub | Thousands of active flags | Ship > Staff Ship > Early Access > GA progressive release |
| Etsy | Pioneer of feature flags | Config flags, ops flags, and experiment flags taxonomy |
| Spotify | Squad-based ownership | Flags tied to team ownership; automated cleanup |
Pattern 1: The Kill Switch
Every external dependency call is wrapped in a flag that can disable it immediately:
if (featureFlags.isEnabled("recommendations-service-enabled")) {
return await recommendationService.getRecommendations(userId);
} else {
return getCachedRecommendations(userId); // Fallback
}
Pattern 2: The Percentage Rollout
New features roll out gradually:
// Week 1: 1% of users
// Week 2: 10% of users
// Week 3: 50% of users
// Week 4: 100% of users
if (featureFlags.isEnabled("new-search-algorithm", { userId })) {
return newSearchAlgorithm(query);
} else {
return legacySearchAlgorithm(query);
}
Pattern 3: The Beta Feature
Premium or early-access users get features first:
if (featureFlags.isEnabled("advanced-analytics", {
userId,
subscriptionTier: user.tier
})) {
showAdvancedAnalyticsDashboard();
}
We've established the foundational understanding of feature flags. Let's consolidate the key takeaways:
What's next:
Now that we understand what feature flags are, we'll explore how to design them well. The next page examines feature flag design patterns—the specific architectural approaches for implementing flags in your codebase while maintaining testability, readability, and performance.
You now understand the fundamental concepts of feature flags: their definition, types, architecture, benefits, and risks. This foundation prepares you for designing, implementing, and governing feature flags in real systems. Next, we'll explore the patterns that make feature flags maintainable and effective.