Skip to main content

Feature Flags and Toggles

Use feature flags to decouple deployment from release and enable safer experimentation.

TL;DR

Feature flags decouple code deployment from feature release. Deploy code with new features disabled, then toggle them on gradually—for specific users, percentages, or regions. This enables safer deployments: if something breaks, flip a flag rather than rolling back and redeploying. Use flags for experimentation: A/B testing, canary releases, and gradual rollouts. However, flag code clutters production code, so manage flag lifecycle carefully—delete flags when they're no longer needed. Feature flags are powerful, but without discipline, they become technical debt.

Learning Objectives

  • Distinguish between deployment (getting code to servers) and release (making features visible)
  • Design feature flags for safe gradual rollouts and experimentation
  • Implement flag evaluation patterns efficiently
  • Manage flag lifecycle to prevent accumulated technical debt
  • Use flags for canary releases, A/B testing, and gradual rollouts
  • Balance flexibility with code clarity

Motivating Scenario

A critical bug appears in production. The team has two choices: rollback (losing hours of good features deployed alongside the buggy one) or fix-and-redeploy (hours of testing). Neither option is ideal. With feature flags, they could disable the buggy feature instantly while good features remain active. Later, when the bug is fixed, they re-enable the feature. This is the power of decoupling deployment from release.

Core Concepts

Deployment vs. Release

Deployment is getting code to servers. Release is making features visible to users. Feature flags separate these concerns. Deploy code with flags controlling visibility, then release features on your own schedule.

Flag Types

Operational flags control performance characteristics (cache enabled, batch size). Permission flags control access (beta features for early adopters). Experiment flags enable A/B testing and canary releases. Each requires different lifecycle and evaluation strategies.

Flag Evaluation

Evaluate flags dynamically at runtime based on context: user ID, environment, percentage, etc. Don't hardcode flag decisions. Store flag configurations externally so they can change without redeployment.

Practical Example

# ❌ POOR - Hardcoded feature gate
def get_checkout_page(user):
if user.id == "admin": # Hardcoded!
return render_new_checkout(user)
return render_legacy_checkout(user)

# ✅ EXCELLENT - Feature flag with external configuration
from enum import Enum
from typing import Optional

class FeatureFlagService:
"""Manage feature flags with external configuration."""

def __init__(self, config_service):
self.config = config_service

def is_enabled(self, flag_name: str, user_id: Optional[str] = None) -> bool:
"""Check if a feature flag is enabled for a user."""
flag_config = self.config.get_flag(flag_name)
if not flag_config:
return False

# Disabled globally
if not flag_config.get('enabled', False):
return False

# Check percentage rollout (consistent hashing)
if 'percentage' in flag_config:
rollout_percent = flag_config['percentage']
if hash(f"{flag_name}:{user_id}") % 100 < rollout_percent:
return True
return False

# Check user whitelist
if 'allowed_users' in flag_config:
return user_id in flag_config['allowed_users']

# Check user group/segment
if 'user_groups' in flag_config:
groups = self.config.get_user_groups(user_id)
return any(g in flag_config['user_groups'] for g in groups)

return True

def set_flag(self, flag_name: str, enabled: bool, **kwargs):
"""Update flag configuration."""
self.config.update_flag(flag_name, {'enabled': enabled, **kwargs})

# Usage
flags = FeatureFlagService(config_service)

def get_checkout_page(user):
if flags.is_enabled('new_checkout_ui', user.id):
return render_new_checkout(user)
return render_legacy_checkout(user)

def list_products(filters):
products = Product.all()

if flags.is_enabled('advanced_filtering'):
products = products.filter(**filters)
else:
products = products.filter(category=filters.get('category'))

return products

Feature Flag Patterns

Canary Releases

// Deploy to 5% of users first, monitor, then increase
const canaryConfig = {
feature_name: {
enabled: true,
percentage: 5, // Start with 5%
}
};

// After monitoring for 24 hours
canaryConfig.feature_name.percentage = 25; // Increase to 25%

// After 48 hours
canaryConfig.feature_name.percentage = 100; // Release to everyone

A/B Testing

const abTestConfig = {
checkout_variant: {
enabled: true,
variants: {
control: { percentage: 50 },
test: { percentage: 50 }
}
}
};

function getCheckoutVariant(userId) {
const variant = hashUser(userId) % 2 === 0 ? 'control' : 'test';
return variant;
}

Permission-Based Gates

const betaFeatureConfig = {
beta_payment_method: {
enabled: true,
betaUsers: ['user_123', 'user_456', 'org_789']
}
};

function canAccessBetaFeature(userId, orgId) {
const config = getConfig('beta_payment_method');
return config.betaUsers.includes(userId) ||
config.betaUsers.includes(orgId);
}

Managing Flag Lifecycle

// Flag lifecycle: create -> test -> deploy -> rollout -> monitor -> remove

// 1. Flag created but disabled (dev phase)
const flag = { name: 'new_feature', enabled: false, created_at: '2025-09-10' };

// 2. Enabled for small group (testing)
flag.enabled = true;
flag.allowedUsers = ['tester_1', 'tester_2'];

// 3. Percentage rollout (gradual release)
flag.percentage = 10; // 10% of users

// 4. Monitor metrics, then expand
flag.percentage = 50;

// 5. Remove flag (flag is fully active)
// Delete flag config, code path with flag becomes standard path
// Must clean up conditional code that checks for flag

Design Review Checklist

  • Is flag configuration external, not hardcoded?
  • Can flags be toggled without redeployment?
  • Are flag names clear and descriptive?
  • Is there a process for removing flags once fully released?
  • Are flags consistent for the same user (hashing for percentage rollout)?
  • Is flag evaluation performant (cached, not querying external service on every call)?
  • Are deprecated flags cleaned up regularly?
  • Is there monitoring to track flag usage and impact?

Self-Check

  1. Identify a risky feature being deployed. How would feature flags make deployment safer?

  2. What flag lifecycle process exists in your team? How do old flags get removed?

  3. Design a canary release strategy for a payment feature using percentage-based rollout.

One Takeaway

Feature flags transform deployment from binary (on or off for everyone) to granular (on for percentage of users, groups, or specific users). This enables safer releases: deploy with new features dark, validate with small groups, then gradually expand. However, flag code must be managed carefully—flags that aren't eventually removed become technical debt cluttering production code. Establish a process for regular flag cleanup.

Next Steps

References

  1. Fowler, M. (2015). Feature Toggles. Retrieved from https://martinfowler.com/articles/feature-toggles.html
  2. Humble, J., & Farley, D. (2010). Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley.
  3. Hodges, B. (2016). Releasing Software with Feature Flags. Retrieved from https://launchdarkly.com/
  4. Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.