| name | feature-flag-system |
| description | Implement feature flags (toggles) for controlled feature rollouts, A/B testing, canary deployments, and kill switches. Use when deploying new features gradually, testing in production, or managing feature lifecycles. |
Feature Flag System
Overview
Implement feature flags to decouple deployment from release, enable gradual rollouts, A/B testing, and provide emergency kill switches.
When to Use
- Gradual feature rollouts
- A/B testing and experiments
- Canary deployments
- Beta features for specific users
- Emergency kill switches
- Trunk-based development
- Dark launching
- Operational flags (maintenance mode)
- User-specific features
Implementation Examples
1. Feature Flag Service (TypeScript)
interface FlagConfig {
key: string;
enabled: boolean;
description: string;
rules?: FlagRule[];
variants?: FlagVariant[];
createdAt: Date;
updatedAt: Date;
}
interface FlagRule {
type: 'user' | 'percentage' | 'attribute' | 'datetime';
operator: 'in' | 'equals' | 'contains' | 'gt' | 'lt' | 'between';
attribute?: string;
values: any[];
}
interface FlagVariant {
key: string;
weight: number;
value: any;
}
interface EvaluationContext {
userId?: string;
email?: string;
attributes?: Record<string, any>;
timestamp?: number;
}
class FeatureFlagService {
private flags: Map<string, FlagConfig> = new Map();
constructor() {
this.loadFlags();
}
private loadFlags(): void {
this.flags.set('new-dashboard', {
key: 'new-dashboard',
enabled: true,
description: 'New dashboard UI',
rules: [
{
type: 'percentage',
operator: 'lt',
values: [25]
}
],
createdAt: new Date(),
updatedAt: new Date()
});
this.flags.set('premium-features', {
key: 'premium-features',
enabled: true,
description: 'Premium features for paid users',
rules: [
{
type: 'attribute',
operator: 'equals',
attribute: 'plan',
values: ['premium', 'enterprise']
}
],
createdAt: new Date(),
updatedAt: new Date()
});
this.flags.set('beta-feature', {
key: 'beta-feature',
enabled: true,
description: 'Beta feature',
rules: [
{
type: 'user',
operator: 'in',
values: ['user1', 'user2', 'user3']
}
],
createdAt: new Date(),
updatedAt: new Date()
});
}
isEnabled(flagKey: string, context: EvaluationContext = {}): boolean {
const flag = this.flags.get(flagKey);
if (!flag) {
console.warn(`Flag not found: ${flagKey}`);
return false;
}
if (!flag.enabled) {
return false;
}
if (!flag.rules || flag.rules.length === 0) {
return true;
}
return this.evaluateRules(flag.rules, context);
}
getVariant(flagKey: string, context: EvaluationContext = {}): any {
const flag = this.flags.get(flagKey);
if (!flag || !this.isEnabled(flagKey, context)) {
return null;
}
if (!flag.variants || flag.variants.length === 0) {
return true;
}
return this.selectVariant(flag.variants, context);
}
private evaluateRules(rules: FlagRule[], context: EvaluationContext): boolean {
return rules.every(rule => this.evaluateRule(rule, context));
}
private evaluateRule(rule: FlagRule, context: EvaluationContext): boolean {
switch (rule.type) {
case 'user':
return this.evaluateUserRule(rule, context);
case 'percentage':
return this.evaluatePercentageRule(rule, context);
case 'attribute':
return this.evaluateAttributeRule(rule, context);
case 'datetime':
return this.evaluateDateTimeRule(rule, context);
default:
return false;
}
}
private evaluateUserRule(rule: FlagRule, context: EvaluationContext): boolean {
if (!context.userId) return false;
return rule.values.includes(context.userId);
}
private evaluatePercentageRule(rule: FlagRule, context: EvaluationContext): boolean {
const hash = this.hashContext(context);
const percentage = (hash % 100) + 1;
return percentage <= rule.values[0];
}
private evaluateAttributeRule(rule: FlagRule, context: EvaluationContext): boolean {
if (!rule.attribute || !context.attributes) return false;
const value = context.attributes[rule.attribute];
switch (rule.operator) {
case 'equals':
return rule.values.includes(value);
case 'contains':
return rule.values.some(v => String(value).includes(v));
case 'gt':
return value > rule.values[0];
case 'lt':
return value < rule.values[0];
default:
return false;
}
}
private evaluateDateTimeRule(rule: FlagRule, context: EvaluationContext): boolean {
const now = context.timestamp || Date.now();
if (rule.operator === 'between') {
return now >= rule.values[0] && now <= rule.values[1];
}
return false;
}
private selectVariant(variants: FlagVariant[], context: EvaluationContext): any {
const hash = this.hashContext(context);
const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
const position = hash % totalWeight;
let cumulative = 0;
for (const variant of variants) {
cumulative += variant.weight;
if (position < cumulative) {
return variant.value;
}
}
return variants[0].value;
}
private hashContext(context: EvaluationContext): number {
const str = context.userId || context.email || 'anonymous';
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash);
}
async createFlag(config: Omit<FlagConfig, 'createdAt' | 'updatedAt'>): Promise<void> {
this.flags.set(config.key, {
...config,
createdAt: new Date(),
updatedAt: new Date()
});
}
async updateFlag(key: string, updates: Partial<FlagConfig>): Promise<void> {
const flag = this.flags.get(key);
if (!flag) {
throw new Error(`Flag not found: ${key}`);
}
this.flags.set(key, {
...flag,
...updates,
updatedAt: new Date()
});
}
async deleteFlag(key: string): Promise<void> {
this.flags.delete(key);
}
getAllFlags(): FlagConfig[] {
return Array.from(this.flags.values());
}
}
const featureFlags = new FeatureFlagService();
if (featureFlags.isEnabled('new-dashboard', { userId: 'user123' })) {
console.log('Show new dashboard');
}
const hasPremiumFeatures = featureFlags.isEnabled('premium-features', {
userId: 'user123',
attributes: { plan: 'premium' }
});
const buttonColor = featureFlags.getVariant('button-color-test', {
userId: 'user123'
});
2. React Hook for Feature Flags
import { createContext, useContext, ReactNode } from 'react';
interface FeatureFlagContextType {
isEnabled: (key: string) => boolean;
getVariant: (key: string) => any;
}
const FeatureFlagContext = createContext<FeatureFlagContextType | null>(null);
export function FeatureFlagProvider({
children,
userId,
attributes
}: {
children: ReactNode;
userId?: string;
attributes?: Record<string, any>;
}) {
const flagService = new FeatureFlagService();
const context: FeatureFlagContextType = {
isEnabled: (key: string) => {
return flagService.isEnabled(key, { userId, attributes });
},
getVariant: (key: string) => {
return flagService.getVariant(key, { userId, attributes });
}
};
return (
);
}
(): {
context = ();
(!context) {
();
}
context.(key);
}
(): {
context = ();
(!context) {
();
}
context.(key);
}
() {
isEnabled = (flag);
isEnabled ? : ;
}
() {
hasNewDashboard = ();
theme = ();
(
);
}
3. Feature Flag with Analytics
interface FlagEvaluationEvent {
flagKey: string;
userId?: string;
result: boolean;
variant?: any;
timestamp: number;
duration: number;
}
class FeatureFlagServiceWithAnalytics extends FeatureFlagService {
private analytics: Analytics;
constructor(analytics: Analytics) {
super();
this.analytics = analytics;
}
isEnabled(flagKey: string, context: EvaluationContext = {}): boolean {
const startTime = Date.now();
const result = super.isEnabled(flagKey, context);
const duration = Date.now() - startTime;
this.trackEvaluation({
flagKey,
userId: context.userId,
result,
timestamp: Date.now(),
duration
});
result;
}
(: , : = {}): {
startTime = .();
variant = .(flagKey, context);
duration = .() - startTime;
.({
flagKey,
: context.,
: variant !== ,
variant,
: .(),
duration
});
variant;
}
(: ): {
..(, {
: event.,
: event.,
: event.,
: event.,
: event.
});
}
(: , : { : ; : }): <{
: ;
: ;
: ;
: <, >;
}> {
..(flagKey, timeRange);
}
}
4. LaunchDarkly-Style SDK
from typing import Dict, Any, Optional
import hashlib
import json
class FeatureFlagClient:
def __init__(self, sdk_key: str, config: Optional[Dict] = None):
self.sdk_key = sdk_key
self.config = config or {}
self.flags: Dict[str, Dict] = {}
self.initialize()
def initialize(self):
"""Load flags from API or cache."""
self.flags = {
'new-feature': {
'enabled': True,
'rollout': {
'percentage': 50
}
},
'premium-feature': {
'enabled': True,
'targeting': {
'attribute': 'plan',
'values': ['premium', 'enterprise']
}
}
}
def variation(
self,
flag_key: ,
user: [, ],
default: =
) -> :
flag = .flags.get(flag_key)
flag flag.get():
default
flag:
._evaluate_targeting(flag[], user):
flag:
._evaluate_rollout(flag[], user, flag_key)
() -> [, ]:
value = .variation(flag_key, user, default)
{
: value,
: value ,
: {
: value
}
}
() -> :
attribute = targeting.get()
values = targeting.get(, [])
user_value = user.get(attribute)
user_value values
() -> :
percentage = rollout.get(, )
user_id = user.get(, user.get(, ))
hash_value = ._hash_user(user_id, flag_key)
bucket = hash_value %
bucket < percentage
() -> :
combined =
hash_bytes = hashlib.sha256(combined.encode()).digest()
.from_bytes(hash_bytes[:], byteorder=)
():
():
():
():
client = FeatureFlagClient(sdk_key=)
user = {
: ,
: ,
:
}
client.variation(, user):
()
detail = client.variation_detail(, user)
()
client.track(, user, {: })
5. Admin UI for Feature Flags
interface FlagFormData {
key: string;
description: string;
enabled: boolean;
rolloutPercentage?: number;
targetUsers?: string[];
targetAttributes?: Record<string, any>;
}
function FeatureFlagDashboard() {
const [flags, setFlags] = useState<FlagConfig[]>([]);
const flagService = new FeatureFlagService();
useEffect(() => {
loadFlags();
}, []);
const loadFlags = async () => {
const allFlags = flagService.getAllFlags();
setFlags(allFlags);
};
const toggleFlag = async (key: string) => {
const flag = flags.find(f => f.key === key);
if (flag) {
await flagService.updateFlag(key, { enabled: !flag.enabled });
await ();
}
};
(
);
}
Best Practices
✅ DO
- Use descriptive flag names
- Document flag purpose and lifecycle
- Implement gradual rollouts
- Track flag evaluations
- Clean up old flags regularly
- Use feature flags for experiments
- Implement kill switches for critical features
- Test both enabled and disabled states
- Use consistent hashing for stable rollouts
- Provide admin UI for non-technical users
❌ DON'T
- Use flags for permanent configuration
- Accumulate technical debt with old flags
- Skip flag cleanup
- Make flags too granular
- Hard-code flag checks everywhere
- Skip analytics and monitoring
Resources