Feature Flags and Progressive Rollouts with Claude Code

This guide explores how to implement feature flags and progressive rollout strategies with Claude Code, enabling safe, controlled feature deployments with real-time monitoring and intelligent rollback capabilities.

Overview

Feature flags and progressive rollouts revolutionize software deployment by:

  • Decoupling deployment from release: Deploy code anytime, release features when ready
  • Reducing risk: Test features with small user segments before full rollout
  • Enabling experimentation: A/B test features and measure impact
  • Providing instant rollback: Disable problematic features without redeployment

Why Feature Flags with Claude Code?

  1. AI-Powered Decision Making: Claude can analyze metrics and recommend rollout strategies
  2. Intelligent Targeting: Use Claude to identify optimal user segments
  3. Automated Monitoring: Claude monitors feature performance and detects anomalies
  4. Smart Rollback: Automatic rollback based on intelligent metric analysis
  5. Natural Language Control: Manage flags through conversational interfaces

Core Concepts

1. Feature Flag Implementation

// feature-flags/FeatureFlag.ts
export interface FeatureFlag {
  id: string;
  name: string;
  description: string;
  enabled: boolean;
  rules: TargetingRule[];
  variants?: Variant[];
  rolloutPercentage?: number;
  createdAt: Date;
  updatedAt: Date;
}
 
export interface TargetingRule {
  id: string;
  attribute: string;
  operator: 'equals' | 'contains' | 'regex' | 'in' | 'percentage';
  values: string[];
  rolloutPercentage?: number;
}
 
export interface Variant {
  id: string;
  name: string;
  weight: number;
  payload?: any;
}
 
// feature-flags/FeatureFlagService.ts
export class FeatureFlagService {
  private flags: Map<string, FeatureFlag> = new Map();
  private evaluationCache: LRUCache<string, boolean>;
  
  constructor() {
    this.evaluationCache = new LRUCache({ max: 10000, ttl: 60000 });
  }
  
  async isEnabled(
    flagKey: string, 
    context: EvaluationContext
  ): Promise<boolean> {
    const cacheKey = `${flagKey}:${JSON.stringify(context)}`;
    
    // Check cache
    if (this.evaluationCache.has(cacheKey)) {
      return this.evaluationCache.get(cacheKey)!;
    }
    
    // Evaluate flag
    const flag = await this.getFlag(flagKey);
    if (!flag || !flag.enabled) {
      return false;
    }
    
    const result = await this.evaluateRules(flag, context);
    this.evaluationCache.set(cacheKey, result);
    
    // Track evaluation
    await this.trackEvaluation(flagKey, context, result);
    
    return result;
  }
  
  async getVariant(
    flagKey: string,
    context: EvaluationContext
  ): Promise<Variant | null> {
    const flag = await this.getFlag(flagKey);
    if (!flag || !flag.enabled || !flag.variants) {
      return null;
    }
    
    // Use consistent hashing for variant assignment
    const hash = this.hash(context.userId || context.sessionId);
    const normalizedHash = hash / 0xFFFFFFFF;
    
    let accumulator = 0;
    for (const variant of flag.variants) {
      accumulator += variant.weight;
      if (normalizedHash <= accumulator) {
        return variant;
      }
    }
    
    return flag.variants[0]; // Fallback
  }
  
  private async evaluateRules(
    flag: FeatureFlag,
    context: EvaluationContext
  ): Promise<boolean> {
    // Check targeting rules
    for (const rule of flag.rules) {
      if (await this.evaluateRule(rule, context)) {
        return true;
      }
    }
    
    // Check rollout percentage
    if (flag.rolloutPercentage !== undefined) {
      const hash = this.hash(context.userId || context.sessionId);
      const percentage = (hash / 0xFFFFFFFF) * 100;
      return percentage <= flag.rolloutPercentage;
    }
    
    return false;
  }
  
  private hash(value: string): number {
    // MurmurHash3 implementation for consistent hashing
    let h = 0;
    for (let i = 0; i < value.length; i++) {
      h = Math.imul(31, h) + value.charCodeAt(i) | 0;
    }
    return Math.abs(h);
  }
}

2. Progressive Rollout Strategy

// rollout/ProgressiveRollout.ts
export class ProgressiveRolloutStrategy {
  private stages: RolloutStage[] = [];
  private currentStageIndex: number = 0;
  private metrics: MetricsCollector;
  
  constructor(
    private flagKey: string,
    private flagService: FeatureFlagService,
    private claudeAnalyzer: ClaudeAnalyzer
  ) {}
  
  async defineStages(stages: RolloutStageConfig[]): Promise<void> {
    this.stages = stages.map(config => ({
      ...config,
      startedAt: null,
      completedAt: null,
      metrics: {},
      status: 'pending'
    }));
  }
  
  async executeRollout(): Promise<void> {
    for (let i = 0; i < this.stages.length; i++) {
      this.currentStageIndex = i;
      const stage = this.stages[i];
      
      // Start stage
      await this.startStage(stage);
      
      // Monitor stage
      const success = await this.monitorStage(stage);
      
      if (!success) {
        await this.rollback();
        throw new Error(`Rollout failed at stage ${i}: ${stage.name}`);
      }
      
      // Complete stage
      await this.completeStage(stage);
    }
  }
  
  private async startStage(stage: RolloutStage): Promise<void> {
    stage.startedAt = new Date();
    stage.status = 'active';
    
    // Update feature flag
    await this.flagService.updateFlag(this.flagKey, {
      rolloutPercentage: stage.percentage,
      rules: stage.targetingRules
    });
    
    // Notify monitoring
    await this.metrics.recordEvent('rollout.stage.started', {
      flagKey: this.flagKey,
      stage: stage.name,
      percentage: stage.percentage
    });
  }
  
  private async monitorStage(stage: RolloutStage): Promise<boolean> {
    const startTime = Date.now();
    
    while (Date.now() - startTime < stage.duration) {
      // Collect metrics
      const metrics = await this.collectMetrics(stage);
      
      // Analyze with Claude
      const analysis = await this.claudeAnalyzer.analyzeRolloutMetrics({
        stage: stage.name,
        metrics,
        thresholds: stage.successCriteria
      });
      
      if (analysis.recommendation === 'rollback') {
        return false;
      }
      
      if (analysis.recommendation === 'accelerate' && stage.canAccelerate) {
        break; // Move to next stage early
      }
      
      // Wait before next check
      await this.sleep(stage.checkInterval || 60000);
    }
    
    return true;
  }
  
  private async collectMetrics(stage: RolloutStage): Promise<StageMetrics> {
    return {
      errorRate: await this.metrics.getErrorRate(stage.startedAt!, new Date()),
      latency: await this.metrics.getAverageLatency(stage.startedAt!, new Date()),
      throughput: await this.metrics.getThroughput(stage.startedAt!, new Date()),
      customMetrics: await this.metrics.getCustomMetrics(
        stage.customMetrics || [],
        stage.startedAt!,
        new Date()
      )
    };
  }
}

3. MCP Server Integration

// mcp-feature-flags-server.ts
import { MCPServer } from '@modelcontextprotocol/sdk';
import { FeatureFlagService } from './feature-flags/FeatureFlagService';
import { ProgressiveRolloutStrategy } from './rollout/ProgressiveRollout';
 
class FeatureFlagsMCPServer extends MCPServer {
  private flagService: FeatureFlagService;
  private rolloutManager: RolloutManager;
  
  async initialize() {
    this.flagService = new FeatureFlagService();
    this.rolloutManager = new RolloutManager(this.flagService);
    
    // Register flag management tools
    this.registerTool({
      name: 'create-feature-flag',
      description: 'Create a new feature flag',
      parameters: {
        type: 'object',
        properties: {
          key: { type: 'string' },
          name: { type: 'string' },
          description: { type: 'string' },
          enabled: { type: 'boolean', default: false },
          rules: { type: 'array', optional: true },
          variants: { type: 'array', optional: true }
        }
      },
      execute: async (params) => {
        const flag = await this.flagService.createFlag(params);
        return { flag };
      }
    });
    
    // Register evaluation tool
    this.registerTool({
      name: 'evaluate-flag',
      description: 'Evaluate a feature flag for a given context',
      parameters: {
        type: 'object',
        properties: {
          flagKey: { type: 'string' },
          context: {
            type: 'object',
            properties: {
              userId: { type: 'string', optional: true },
              sessionId: { type: 'string', optional: true },
              attributes: { type: 'object', optional: true }
            }
          }
        }
      },
      execute: async ({ flagKey, context }) => {
        const enabled = await this.flagService.isEnabled(flagKey, context);
        const variant = await this.flagService.getVariant(flagKey, context);
        return { enabled, variant };
      }
    });
    
    // Register rollout tool
    this.registerTool({
      name: 'start-progressive-rollout',
      description: 'Start a progressive rollout for a feature flag',
      parameters: {
        type: 'object',
        properties: {
          flagKey: { type: 'string' },
          stages: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                name: { type: 'string' },
                percentage: { type: 'number' },
                duration: { type: 'number' },
                targetingRules: { type: 'array', optional: true },
                successCriteria: { type: 'object' }
              }
            }
          }
        }
      },
      execute: async ({ flagKey, stages }) => {
        const rollout = await this.rolloutManager.createRollout(flagKey, stages);
        await rollout.start();
        return { rolloutId: rollout.id, status: 'started' };
      }
    });
    
    // Register Claude analysis tool
    this.registerTool({
      name: 'analyze-feature-impact',
      description: 'Use Claude to analyze feature flag impact',
      parameters: {
        type: 'object',
        properties: {
          flagKey: { type: 'string' },
          timeRange: {
            type: 'object',
            properties: {
              start: { type: 'string' },
              end: { type: 'string' }
            }
          }
        }
      },
      execute: async ({ flagKey, timeRange }) => {
        const analysis = await this.analyzeFeatureImpact(flagKey, timeRange);
        return { analysis };
      }
    });
  }
  
  private async analyzeFeatureImpact(
    flagKey: string,
    timeRange: { start: string; end: string }
  ): Promise<FeatureImpactAnalysis> {
    // Collect metrics
    const metrics = await this.collectFeatureMetrics(flagKey, timeRange);
    
    // Use Claude to analyze
    const prompt = `Analyze the impact of feature flag "${flagKey}" based on these metrics:
    
    ${JSON.stringify(metrics, null, 2)}
    
    Please provide:
    1. Overall impact assessment (positive/negative/neutral)
    2. Key performance indicators affected
    3. User experience impact
    4. Recommendations for full rollout or rollback
    5. Suggested improvements`;
    
    const analysis = await this.claudeClient.analyze(prompt);
    
    return {
      flagKey,
      timeRange,
      metrics,
      assessment: analysis.assessment,
      recommendations: analysis.recommendations,
      suggestedActions: analysis.actions
    };
  }
}

Advanced Patterns

1. Canary Deployment with Feature Flags

// deployment/CanaryDeployment.ts
export class CanaryDeploymentManager {
  private deployments: Map<string, CanaryDeployment> = new Map();
  
  async createCanaryDeployment(
    config: CanaryConfig
  ): Promise<CanaryDeployment> {
    const deployment = new CanaryDeployment(config);
    
    // Create feature flags for canary control
    await this.createCanaryFlags(deployment);
    
    // Start deployment
    await deployment.start();
    
    this.deployments.set(deployment.id, deployment);
    return deployment;
  }
  
  private async createCanaryFlags(deployment: CanaryDeployment): Promise<void> {
    // Traffic routing flag
    await this.flagService.createFlag({
      key: `canary.${deployment.id}.traffic`,
      name: `Canary Traffic - ${deployment.name}`,
      enabled: true,
      rolloutPercentage: deployment.initialPercentage,
      rules: [{
        id: 'canary-users',
        attribute: 'canaryGroup',
        operator: 'equals',
        values: [deployment.id]
      }]
    });
    
    // Feature toggles for canary-specific features
    for (const feature of deployment.features) {
      await this.flagService.createFlag({
        key: `canary.${deployment.id}.feature.${feature}`,
        name: `Canary Feature - ${feature}`,
        enabled: true,
        rules: [{
          id: 'canary-only',
          attribute: 'deploymentVersion',
          operator: 'equals',
          values: [deployment.version]
        }]
      });
    }
  }
}
 
export class CanaryDeployment {
  private metrics: CanaryMetrics;
  private healthChecker: HealthChecker;
  
  constructor(private config: CanaryConfig) {}
  
  async start(): Promise<void> {
    // Start health monitoring
    this.healthChecker.startMonitoring({
      interval: this.config.healthCheckInterval,
      endpoints: this.config.canaryEndpoints
    });
    
    // Progressive traffic increase
    await this.progressiveTrafficIncrease();
  }
  
  private async progressiveTrafficIncrease(): Promise<void> {
    let currentPercentage = this.config.initialPercentage;
    
    while (currentPercentage < 100) {
      // Monitor health
      const health = await this.checkCanaryHealth();
      
      if (!health.isHealthy) {
        await this.rollback();
        throw new Error(`Canary unhealthy: ${health.reason}`);
      }
      
      // Increase traffic
      currentPercentage = Math.min(
        currentPercentage + this.config.incrementPercentage,
        100
      );
      
      await this.updateTrafficPercentage(currentPercentage);
      
      // Wait before next increase
      await this.sleep(this.config.incrementInterval);
    }
    
    // Canary successful - promote to production
    await this.promote();
  }
  
  private async checkCanaryHealth(): Promise<HealthStatus> {
    const metrics = await this.metrics.collect();
    
    // Compare canary vs stable metrics
    const comparison = {
      errorRateDiff: metrics.canary.errorRate - metrics.stable.errorRate,
      latencyDiff: metrics.canary.avgLatency - metrics.stable.avgLatency,
      throughputRatio: metrics.canary.throughput / metrics.stable.throughput
    };
    
    // Check thresholds
    if (comparison.errorRateDiff > this.config.errorRateThreshold) {
      return { isHealthy: false, reason: 'Error rate exceeded threshold' };
    }
    
    if (comparison.latencyDiff > this.config.latencyThreshold) {
      return { isHealthy: false, reason: 'Latency degradation detected' };
    }
    
    if (comparison.throughputRatio < this.config.throughputThreshold) {
      return { isHealthy: false, reason: 'Throughput below threshold' };
    }
    
    return { isHealthy: true };
  }
}

2. A/B Testing Framework

// experiments/ABTestingFramework.ts
export class ABTestingFramework {
  private experiments: Map<string, Experiment> = new Map();
  private analyticsCollector: AnalyticsCollector;
  
  async createExperiment(config: ExperimentConfig): Promise<Experiment> {
    const experiment = new Experiment(config);
    
    // Create feature flag with variants
    await this.flagService.createFlag({
      key: `experiment.${experiment.id}`,
      name: experiment.name,
      enabled: true,
      variants: config.variants.map((v, index) => ({
        id: v.id,
        name: v.name,
        weight: v.allocation,
        payload: v.configuration
      }))
    });
    
    this.experiments.set(experiment.id, experiment);
    return experiment;
  }
  
  async getVariant(
    experimentId: string,
    context: EvaluationContext
  ): Promise<ExperimentVariant> {
    const experiment = this.experiments.get(experimentId);
    if (!experiment || !experiment.isActive()) {
      return experiment?.control || null;
    }
    
    // Get variant from feature flag
    const variant = await this.flagService.getVariant(
      `experiment.${experimentId}`,
      context
    );
    
    if (!variant) {
      return experiment.control;
    }
    
    // Track assignment
    await this.analyticsCollector.trackAssignment({
      experimentId,
      variantId: variant.id,
      userId: context.userId,
      timestamp: new Date()
    });
    
    return {
      id: variant.id,
      name: variant.name,
      configuration: variant.payload
    };
  }
  
  async analyzeResults(experimentId: string): Promise<ExperimentResults> {
    const experiment = this.experiments.get(experimentId);
    if (!experiment) {
      throw new Error(`Experiment ${experimentId} not found`);
    }
    
    // Collect metrics for each variant
    const variantMetrics = await Promise.all(
      experiment.variants.map(async (variant) => {
        const metrics = await this.analyticsCollector.getMetrics({
          experimentId,
          variantId: variant.id,
          metrics: experiment.successMetrics,
          startDate: experiment.startDate,
          endDate: experiment.endDate || new Date()
        });
        
        return { variant, metrics };
      })
    );
    
    // Statistical analysis
    const analysis = await this.performStatisticalAnalysis(variantMetrics);
    
    // Claude interpretation
    const interpretation = await this.getClaudeInterpretation(
      experiment,
      variantMetrics,
      analysis
    );
    
    return {
      experiment,
      variantMetrics,
      analysis,
      interpretation,
      recommendation: interpretation.recommendation
    };
  }
  
  private async performStatisticalAnalysis(
    variantMetrics: VariantMetrics[]
  ): Promise<StatisticalAnalysis> {
    // Implement statistical tests (t-test, chi-square, etc.)
    // Calculate confidence intervals, p-values, effect sizes
    return {
      significantDifferences: [],
      confidenceIntervals: {},
      effectSizes: {},
      sampleSizes: {}
    };
  }
  
  private async getClaudeInterpretation(
    experiment: Experiment,
    metrics: VariantMetrics[],
    analysis: StatisticalAnalysis
  ): Promise<ExperimentInterpretation> {
    const prompt = `Analyze this A/B test experiment:
    
    Experiment: ${experiment.name}
    Hypothesis: ${experiment.hypothesis}
    
    Variant Metrics:
    ${JSON.stringify(metrics, null, 2)}
    
    Statistical Analysis:
    ${JSON.stringify(analysis, null, 2)}
    
    Please provide:
    1. Key findings and insights
    2. Statistical significance interpretation
    3. Business impact assessment
    4. Recommendation (implement winner/continue testing/abandon)
    5. Suggested next steps`;
    
    const response = await this.claudeClient.analyze(prompt);
    
    return {
      findings: response.findings,
      businessImpact: response.businessImpact,
      recommendation: response.recommendation,
      nextSteps: response.nextSteps
    };
  }
}

3. Intelligent Rollback System

// rollback/IntelligentRollback.ts
export class IntelligentRollbackSystem {
  private monitors: Map<string, FeatureMonitor> = new Map();
  private rollbackHistory: RollbackEvent[] = [];
  
  async monitorFeature(
    flagKey: string,
    config: MonitoringConfig
  ): Promise<void> {
    const monitor = new FeatureMonitor(flagKey, config);
    
    monitor.on('anomaly', async (anomaly) => {
      await this.handleAnomaly(flagKey, anomaly);
    });
    
    monitor.start();
    this.monitors.set(flagKey, monitor);
  }
  
  private async handleAnomaly(
    flagKey: string,
    anomaly: Anomaly
  ): Promise<void> {
    // Get Claude's assessment
    const assessment = await this.getClaudeAssessment(flagKey, anomaly);
    
    if (assessment.severity === 'critical') {
      // Immediate rollback
      await this.executeRollback(flagKey, assessment.reason);
    } else if (assessment.severity === 'warning') {
      // Gradual rollback or alert
      if (assessment.recommendedAction === 'gradual-rollback') {
        await this.executeGradualRollback(flagKey, assessment);
      } else {
        await this.alertTeam(flagKey, anomaly, assessment);
      }
    }
  }
  
  private async getClaudeAssessment(
    flagKey: string,
    anomaly: Anomaly
  ): Promise<AnomalyAssessment> {
    const context = await this.gatherContext(flagKey, anomaly);
    
    const prompt = `Assess this anomaly for feature flag "${flagKey}":
    
    Anomaly Details:
    ${JSON.stringify(anomaly, null, 2)}
    
    Historical Context:
    ${JSON.stringify(context, null, 2)}
    
    Please provide:
    1. Severity assessment (critical/warning/info)
    2. Root cause analysis
    3. Recommended action (immediate-rollback/gradual-rollback/monitor/ignore)
    4. Reasoning for recommendation`;
    
    const response = await this.claudeClient.analyze(prompt);
    
    return {
      severity: response.severity,
      reason: response.rootCause,
      recommendedAction: response.action,
      confidence: response.confidence
    };
  }
  
  private async executeRollback(
    flagKey: string,
    reason: string
  ): Promise<void> {
    // Disable feature flag
    await this.flagService.updateFlag(flagKey, { enabled: false });
    
    // Record rollback event
    const event: RollbackEvent = {
      flagKey,
      timestamp: new Date(),
      reason,
      automatic: true,
      metrics: await this.captureMetrics(flagKey)
    };
    
    this.rollbackHistory.push(event);
    
    // Notify stakeholders
    await this.notificationService.send({
      type: 'rollback',
      severity: 'critical',
      flag: flagKey,
      reason,
      action: 'Feature automatically disabled due to critical anomaly'
    });
  }
  
  private async executeGradualRollback(
    flagKey: string,
    assessment: AnomalyAssessment
  ): Promise<void> {
    const currentPercentage = await this.flagService.getRolloutPercentage(flagKey);
    let targetPercentage = currentPercentage;
    
    // Gradually reduce percentage
    const reductionSteps = [50, 25, 10, 5, 0];
    
    for (const step of reductionSteps) {
      if (step >= currentPercentage) continue;
      
      targetPercentage = step;
      await this.flagService.updateFlag(flagKey, {
        rolloutPercentage: targetPercentage
      });
      
      // Monitor for improvement
      await this.sleep(assessment.monitoringInterval || 300000); // 5 minutes
      
      const improved = await this.checkImprovement(flagKey, anomaly);
      if (improved) {
        break; // Stop reduction if metrics improve
      }
    }
  }
}

4. Real-time Configuration Updates

// realtime/RealtimeConfigUpdates.ts
export class RealtimeConfigurationSystem {
  private websocketServer: WebSocketServer;
  private configCache: Map<string, ConfigSnapshot> = new Map();
  private subscribers: Map<string, Set<WebSocket>> = new Map();
  
  async initialize(): Promise<void> {
    this.websocketServer = new WebSocketServer({ port: 8080 });
    
    this.websocketServer.on('connection', (ws) => {
      ws.on('message', async (message) => {
        const { type, payload } = JSON.parse(message.toString());
        
        switch (type) {
          case 'subscribe':
            await this.handleSubscribe(ws, payload);
            break;
          case 'unsubscribe':
            await this.handleUnsubscribe(ws, payload);
            break;
        }
      });
    });
    
    // Listen for flag changes
    this.flagService.on('flag-updated', async (event) => {
      await this.broadcastUpdate(event.flagKey, event.changes);
    });
  }
  
  private async handleSubscribe(
    ws: WebSocket,
    payload: { flags: string[]; context: EvaluationContext }
  ): Promise<void> {
    // Store subscription
    for (const flagKey of payload.flags) {
      if (!this.subscribers.has(flagKey)) {
        this.subscribers.set(flagKey, new Set());
      }
      this.subscribers.get(flagKey)!.add(ws);
    }
    
    // Send initial configuration
    const config = await this.evaluateFlags(payload.flags, payload.context);
    ws.send(JSON.stringify({
      type: 'initial-config',
      payload: config
    }));
    
    // Set up context-aware updates
    ws.on('context-update', async (newContext) => {
      const updatedConfig = await this.evaluateFlags(
        payload.flags,
        newContext
      );
      ws.send(JSON.stringify({
        type: 'config-update',
        payload: updatedConfig
      }));
    });
  }
  
  private async broadcastUpdate(
    flagKey: string,
    changes: FlagChanges
  ): Promise<void> {
    const subscribers = this.subscribers.get(flagKey);
    if (!subscribers) return;
    
    // Notify all subscribers
    for (const ws of subscribers) {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({
          type: 'flag-update',
          payload: {
            flagKey,
            changes,
            timestamp: new Date()
          }
        }));
      }
    }
  }
}
 
// Client SDK
export class FeatureFlagClient {
  private ws: WebSocket;
  private flags: Map<string, boolean> = new Map();
  private variants: Map<string, Variant> = new Map();
  private callbacks: Map<string, Set<Function>> = new Map();
  
  async connect(url: string, context: EvaluationContext): Promise<void> {
    this.ws = new WebSocket(url);
    
    this.ws.on('open', () => {
      this.subscribe(Array.from(this.callbacks.keys()), context);
    });
    
    this.ws.on('message', (data) => {
      const { type, payload } = JSON.parse(data.toString());
      
      switch (type) {
        case 'initial-config':
        case 'config-update':
          this.updateLocalFlags(payload);
          break;
        case 'flag-update':
          this.handleFlagUpdate(payload);
          break;
      }
    });
  }
  
  isEnabled(flagKey: string): boolean {
    return this.flags.get(flagKey) || false;
  }
  
  getVariant(flagKey: string): Variant | null {
    return this.variants.get(flagKey) || null;
  }
  
  onChange(flagKey: string, callback: (enabled: boolean) => void): void {
    if (!this.callbacks.has(flagKey)) {
      this.callbacks.set(flagKey, new Set());
    }
    this.callbacks.get(flagKey)!.add(callback);
  }
  
  private updateLocalFlags(config: FlagConfiguration): void {
    for (const [key, value] of Object.entries(config.flags)) {
      const oldValue = this.flags.get(key);
      this.flags.set(key, value.enabled);
      
      if (value.variant) {
        this.variants.set(key, value.variant);
      }
      
      // Notify callbacks if value changed
      if (oldValue !== value.enabled) {
        const callbacks = this.callbacks.get(key);
        if (callbacks) {
          callbacks.forEach(cb => cb(value.enabled));
        }
      }
    }
  }
}

Testing Strategies

1. Feature Flag Testing

// tests/FeatureFlagTesting.ts
import { FeatureFlagTestHarness } from '../testing/FeatureFlagTestHarness';
 
describe('Feature Flag Integration', () => {
  let harness: FeatureFlagTestHarness;
  
  beforeEach(() => {
    harness = new FeatureFlagTestHarness();
  });
  
  it('should respect targeting rules', async () => {
    // Create flag with targeting
    await harness.createFlag({
      key: 'premium-feature',
      rules: [{
        attribute: 'plan',
        operator: 'equals',
        values: ['premium', 'enterprise']
      }]
    });
    
    // Test different contexts
    expect(await harness.evaluate('premium-feature', {
      userId: 'user1',
      attributes: { plan: 'free' }
    })).toBe(false);
    
    expect(await harness.evaluate('premium-feature', {
      userId: 'user2',
      attributes: { plan: 'premium' }
    })).toBe(true);
  });
  
  it('should handle percentage rollouts consistently', async () => {
    await harness.createFlag({
      key: 'new-feature',
      rolloutPercentage: 50
    });
    
    // Test consistency
    const userId = 'consistent-user';
    const firstEval = await harness.evaluate('new-feature', { userId });
    
    // Same user should always get same result
    for (let i = 0; i < 10; i++) {
      const result = await harness.evaluate('new-feature', { userId });
      expect(result).toBe(firstEval);
    }
  });
  
  it('should support variant distribution', async () => {
    await harness.createFlag({
      key: 'experiment',
      variants: [
        { id: 'control', name: 'Control', weight: 0.5 },
        { id: 'variant-a', name: 'Variant A', weight: 0.3 },
        { id: 'variant-b', name: 'Variant B', weight: 0.2 }
      ]
    });
    
    // Test distribution
    const distribution = await harness.testVariantDistribution(
      'experiment',
      10000
    );
    
    expect(distribution['control']).toBeCloseTo(0.5, 1);
    expect(distribution['variant-a']).toBeCloseTo(0.3, 1);
    expect(distribution['variant-b']).toBeCloseTo(0.2, 1);
  });
});

2. Rollout Testing

// tests/RolloutTesting.ts
describe('Progressive Rollout', () => {
  let rolloutSimulator: RolloutSimulator;
  
  beforeEach(() => {
    rolloutSimulator = new RolloutSimulator();
  });
  
  it('should handle rollback on error spike', async () => {
    const rollout = await rolloutSimulator.createRollout({
      stages: [
        { percentage: 10, duration: 60000, errorThreshold: 0.05 },
        { percentage: 50, duration: 60000, errorThreshold: 0.05 },
        { percentage: 100, duration: 60000, errorThreshold: 0.05 }
      ]
    });
    
    // Simulate error spike in stage 2
    rolloutSimulator.on('stage-started', (stage) => {
      if (stage.index === 1) {
        rolloutSimulator.injectErrors({ rate: 0.1 }); // 10% error rate
      }
    });
    
    await expect(rollout.execute()).rejects.toThrow(/Rollout failed/);
    expect(rollout.getCurrentPercentage()).toBe(0); // Should rollback
  });
  
  it('should accelerate on positive metrics', async () => {
    const rollout = await rolloutSimulator.createRollout({
      stages: [
        { 
          percentage: 10, 
          duration: 300000, // 5 minutes
          canAccelerate: true,
          accelerationCriteria: {
            errorRate: { max: 0.01 },
            latency: { p99: 100 },
            customMetric: { min: 0.95 }
          }
        }
      ]
    });
    
    // Simulate excellent metrics
    rolloutSimulator.setMetrics({
      errorRate: 0.005,
      latency: { p99: 50 },
      customMetric: 0.98
    });
    
    const startTime = Date.now();
    await rollout.execute();
    const duration = Date.now() - startTime;
    
    // Should complete faster than planned duration
    expect(duration).toBeLessThan(300000);
  });
});

Monitoring and Analytics

1. Feature Flag Analytics

// analytics/FeatureFlagAnalytics.ts
export class FeatureFlagAnalytics {
  private metricsCollector: MetricsCollector;
  private eventStore: EventStore;
  
  async trackEvaluation(
    flagKey: string,
    context: EvaluationContext,
    result: boolean,
    variant?: Variant
  ): Promise<void> {
    const event: EvaluationEvent = {
      type: 'flag-evaluation',
      flagKey,
      userId: context.userId,
      sessionId: context.sessionId,
      result,
      variant: variant?.id,
      context: context.attributes,
      timestamp: new Date()
    };
    
    await this.eventStore.store(event);
    
    // Update metrics
    this.metricsCollector.increment(`flag.${flagKey}.evaluations`);
    this.metricsCollector.increment(`flag.${flagKey}.${result ? 'enabled' : 'disabled'}`);
    
    if (variant) {
      this.metricsCollector.increment(`flag.${flagKey}.variant.${variant.id}`);
    }
  }
  
  async generateReport(
    flagKey: string,
    timeRange: TimeRange
  ): Promise<FlagAnalyticsReport> {
    const events = await this.eventStore.query({
      type: 'flag-evaluation',
      flagKey,
      timeRange
    });
    
    const report: FlagAnalyticsReport = {
      flagKey,
      timeRange,
      totalEvaluations: events.length,
      uniqueUsers: new Set(events.map(e => e.userId)).size,
      enabledPercentage: events.filter(e => e.result).length / events.length,
      variantDistribution: this.calculateVariantDistribution(events),
      evaluationTrend: this.calculateTrend(events),
      userSegments: await this.analyzeUserSegments(events)
    };
    
    // Get Claude insights
    report.insights = await this.getClaudeInsights(report);
    
    return report;
  }
  
  private async getClaudeInsights(
    report: FlagAnalyticsReport
  ): Promise<AnalyticsInsights> {
    const prompt = `Analyze this feature flag usage report:
    
    ${JSON.stringify(report, null, 2)}
    
    Please provide:
    1. Usage patterns and trends
    2. Adoption rate analysis
    3. User segment insights
    4. Recommendations for optimization
    5. Potential issues or concerns`;
    
    const response = await this.claudeClient.analyze(prompt);
    
    return {
      patterns: response.patterns,
      adoptionAnalysis: response.adoption,
      segmentInsights: response.segments,
      recommendations: response.recommendations,
      concerns: response.concerns
    };
  }
}

2. Performance Monitoring

// monitoring/PerformanceMonitoring.ts
export class FeatureFlagPerformanceMonitor {
  private performanceObserver: PerformanceObserver;
  
  async monitorFlagPerformance(): Promise<void> {
    // Track evaluation latency
    this.performanceObserver = new PerformanceObserver((entries) => {
      for (const entry of entries.getEntries()) {
        if (entry.name.startsWith('flag-evaluation:')) {
          const flagKey = entry.name.split(':')[1];
          this.metricsCollector.histogram(
            `flag.${flagKey}.evaluation.duration`,
            entry.duration
          );
          
          // Alert on slow evaluations
          if (entry.duration > 50) { // 50ms threshold
            this.alerting.warn({
              type: 'slow-flag-evaluation',
              flagKey,
              duration: entry.duration,
              threshold: 50
            });
          }
        }
      }
    });
    
    this.performanceObserver.observe({ entryTypes: ['measure'] });
  }
  
  async measureFlagEvaluation<T>(
    flagKey: string,
    evaluation: () => Promise<T>
  ): Promise<T> {
    const startMark = `flag-start:${flagKey}:${Date.now()}`;
    const endMark = `flag-end:${flagKey}:${Date.now()}`;
    
    performance.mark(startMark);
    
    try {
      const result = await evaluation();
      performance.mark(endMark);
      performance.measure(`flag-evaluation:${flagKey}`, startMark, endMark);
      
      return result;
    } catch (error) {
      performance.mark(endMark);
      performance.measure(`flag-evaluation-error:${flagKey}`, startMark, endMark);
      throw error;
    }
  }
}

Best Practices

  1. Flag Naming Convention

    • Use hierarchical naming: product.feature.subfeature
    • Include purpose: experiment.checkout.one-click
    • Avoid generic names
  2. Lifecycle Management

    • Set expiration dates for temporary flags
    • Clean up obsolete flags regularly
    • Document flag purpose and ownership
  3. Testing Strategy

    • Test all flag variations
    • Include flag states in integration tests
    • Use flag overrides for testing
  4. Monitoring and Alerts

    • Monitor flag evaluation performance
    • Track business metrics per flag state
    • Set up anomaly detection
  5. Progressive Rollout

    • Start with internal users
    • Monitor key metrics at each stage
    • Have clear rollback criteria

Common Pitfalls

  1. Flag Debt: Accumulating unused flags
  2. Complex Dependencies: Flags depending on other flags
  3. Missing Context: Not tracking enough data for decisions
  4. Poor Performance: Not caching flag evaluations
  5. Inconsistent Experience: Users seeing different states

References

0 items under this folder.