Skip to main content
🏗️Architecture Patterns

Feature Flags: Safe and Controlled Deployments

Feature flags enable teams to deploy code safely by controlling feature visibility at runtime. Learn about flag types, implementation patterns, and best practices...

📖 10 min read

Feature Flags: Safe and Controlled Deployments

Feature flags (also called feature toggles or feature switches) are one of the most powerful techniques in modern software engineering. They allow you to decouple deployment from release, giving your team the ability to ship code to production without exposing it to users until you are ready. This simple concept has profound implications for how you build, test, and deliver software.

If you have ever been stuck in a long-lived feature branch, sweating over a risky deployment, or wishing you could instantly roll back a change — feature flags are the answer. In this guide, we will cover the types of feature flags, implementation patterns, gradual rollout strategies, and how to manage the technical debt they can introduce. For related deployment concepts, see Deployment Strategies and CI/CD Pipeline Design.

What Are Feature Flags?

A feature flag is a conditional statement in your code that controls whether a feature is active or inactive. At its simplest, it looks like this:

if (featureFlags.isEnabled("new-checkout-flow")) {
  renderNewCheckout();
} else {
  renderLegacyCheckout();
}

The flag value can come from a configuration file, a database, an environment variable, or a dedicated feature flag service. The key insight is that the code for both the old and new behavior is deployed together, but only one path is active at runtime.

Types of Feature Flags

Not all feature flags are created equal. Martin Fowler and Pete Hodgson categorize them into four types based on their longevity and dynamism:

TypePurposeLifespanWho ControlsExample
Release FlagsDecouple deployment from releaseDays to weeksEngineeringEnable new search algorithm
Experiment FlagsA/B testing and multivariate testsWeeks to monthsProduct/DataTest two pricing page layouts
Ops FlagsOperational control (kill switches)IndefiniteOperations/SREDisable non-critical features under load
Permission FlagsEntitlements and premium featuresIndefiniteProduct/BusinessEnable beta features for enterprise tier

Release Flags

Release flags are the most common type. They allow you to merge incomplete features into your main branch without exposing them to users. This enables trunk-based development, where all developers commit to a single branch and avoid long-lived feature branches.

Release flags should be short-lived. Once the feature is fully rolled out and stable, remove the flag and the old code path. Leaving release flags in place for months is a common source of technical debt.

Experiment Flags

Experiment flags are used for A/B testing. They route different users to different code paths and measure which variant performs better. Unlike release flags, experiment flags are inherently multi-variant — they do not just toggle between on and off, they select among multiple options.

const variant = featureFlags.getVariant("pricing-page-test", userId);
// variant could be "control", "variant-a", or "variant-b"

switch (variant) {
  case "variant-a":
    return renderNewPricingLayout();
  case "variant-b":
    return renderMinimalPricingLayout();
  default:
    return renderCurrentPricingLayout();
}

Ops Flags and Kill Switches

Ops flags give your operations team the ability to degrade gracefully under load. A classic example is a kill switch that disables recommendation engines, search suggestions, or other non-critical features when the system is under stress. These flags are long-lived and should be treated as part of your system's operational controls.

Permission Flags

Permission flags control access to features based on user attributes — subscription tier, role, organization, or geographic location. They are essentially entitlement checks and are typically the longest-lived flags in your system.

Implementation Patterns

There are several ways to implement feature flags, ranging from simple to sophisticated.

Static Configuration

The simplest approach is to use environment variables or configuration files:

# .env file
FEATURE_NEW_CHECKOUT=true
FEATURE_DARK_MODE=false
// Read from environment
const isNewCheckoutEnabled = process.env.FEATURE_NEW_CHECKOUT === "true";

This works for small teams but requires a redeployment to change flag values. It is not suitable for gradual rollouts or user-level targeting.

Database-Backed Flags

Store flag values in a database and read them at runtime. This allows you to change flag values without redeploying, but you need to handle caching and consistency:

class FeatureFlagService {
  private cache: Map<string, boolean> = new Map();
  private ttl: number = 30000; // 30 seconds

  async isEnabled(flagName: string): Promise<boolean> {
    if (this.cache.has(flagName)) {
      return this.cache.get(flagName)!;
    }
    const flag = await db.query(
      "SELECT enabled FROM feature_flags WHERE name = ?",
      [flagName]
    );
    this.cache.set(flagName, flag.enabled);
    setTimeout(() => this.cache.delete(flagName), this.ttl);
    return flag.enabled;
  }
}

Dedicated Feature Flag Service

For production systems, use a dedicated feature flag service that handles targeting rules, percentage rollouts, and real-time updates. The evaluation flow typically looks like this:

// Using a feature flag SDK
const client = new FeatureFlagClient({
  sdkKey: "your-sdk-key",
  user: { id: userId, email: userEmail, plan: "enterprise" }
});

// Evaluate flag with user context
const showNewDashboard = client.isEnabled("new-dashboard", {
  userId: currentUser.id,
  attributes: {
    plan: currentUser.plan,
    country: currentUser.country,
    registeredAt: currentUser.createdAt
  }
});

Gradual Rollouts

One of the most valuable capabilities of feature flags is the ability to gradually roll out a feature to increasing percentages of users. A typical rollout plan looks like this:

PhaseAudiencePercentageDurationGoal
1Internal team0.1%1-2 daysSmoke test in production
2Beta users5%3-5 daysCollect feedback
3Early adopters25%1 weekMonitor metrics
4General availability50%1 weekValidate at scale
5Full rollout100%PermanentComplete release

The key is consistent hashing. You want the same user to always see the same variant. A simple approach is to hash the user ID and flag name together:

function isEnabledForUser(flagName: string, userId: string, percentage: number): boolean {
  const hash = murmurhash3(flagName + userId);
  const bucket = hash % 100;
  return bucket < percentage;
}

// At 25% rollout, users with buckets 0-24 see the feature
// At 50% rollout, users with buckets 0-49 see the feature
// Users who had the feature at 25% still have it at 50%

A/B Testing with Feature Flags

Feature flags are the backbone of A/B testing. The flag service assigns users to variants, and your analytics pipeline measures the impact. A well-structured experiment requires:

  • Hypothesis: What you expect to happen ("New checkout will increase conversion by 5%")
  • Primary metric: The metric you are optimizing (conversion rate)
  • Guardrail metrics: Metrics that must not degrade (page load time, error rate)
  • Sample size: Enough users to reach statistical significance
  • Duration: Long enough to account for day-of-week effects (typically 1-2 weeks minimum)

For more on data processing pipelines that support A/B testing analytics, see Lambda and Kappa Architecture.

Managing Feature Flag Technical Debt

Feature flags are powerful, but they come with a cost. Every flag is a branch in your code — it increases complexity, makes testing harder, and can lead to subtle bugs when flags interact with each other in unexpected ways.

Flag Lifecycle Management

Establish a clear lifecycle for every flag:

  1. Creation: Document the flag, its purpose, owner, and expected removal date
  2. Rollout: Gradually increase the percentage
  3. Stabilization: Monitor metrics for 1-2 weeks at 100%
  4. Cleanup: Remove the flag, the old code path, and any targeting rules

Set a policy: release flags must be removed within 30 days of reaching 100% rollout. Track flag age in your dashboards and alert on stale flags.

Testing with Feature Flags

Every flag doubles the number of code paths you need to test. With 10 independent flags, you have 1024 possible combinations. To manage this complexity:

  • Test each flag in both states (on and off) independently
  • Identify flags that interact and test those combinations explicitly
  • Use flag overrides in your test environment to exercise specific paths
  • Run integration tests with flags set to their production values

Feature Flag Tools and Platforms

Several mature platforms provide feature flag management out of the box:

ToolTypeKey FeaturesBest For
LaunchDarklySaaSReal-time updates, targeting, experimentsEnterprise teams
UnleashOpen SourceSelf-hosted, activation strategiesTeams wanting control
FlagsmithOpen Source / SaaSRemote config, segments, audit logsFlexible deployment
SplitSaaSFeature delivery + experimentationData-driven teams
ConfigCatSaaSSimple setup, percentage rolloutsSmall to mid teams

Best Practices

  • Name flags descriptively: Use names like enable-new-checkout-flow rather than flag-123
  • Centralize flag evaluation: Do not scatter flag checks throughout your codebase — use a single evaluation point per feature
  • Log flag evaluations: Record which flags were evaluated and their values for every request — this is essential for debugging
  • Set expiration dates: Every release flag should have an owner and a removal deadline
  • Avoid flag dependencies: Flag A should not depend on flag B being enabled — this creates invisible coupling
  • Use server-side evaluation: Evaluate flags on the server to prevent users from manipulating client-side flags

Feature flags transform how you deliver software. They give you the confidence to deploy frequently, the ability to test in production, and the safety net of instant rollback. But like any powerful tool, they require discipline — create flags deliberately, roll them out carefully, and clean them up promptly. Combined with solid CI/CD pipelines and smart deployment strategies, feature flags are a cornerstone of modern software delivery.

Related Articles