Feature Flags for Legacy Systems

Safe deployments, gradual rollouts, and risk-free releases

The deployment risk problem

You ship code to production. It breaks. Now you're scrambling to rollback.

What if you could deploy code but control when it goes live?

Deploy vs Release

Deploy: Push code to production servers

Release: Make features visible to users

Feature flags decouple these two actions.

You can deploy on Monday. Release on Friday. Rollback instantly without redeploying.

What are feature flags?

Runtime configuration switches that enable or disable functionality without deploying new code.

function processPayment(order: Order) {
  if (featureFlags.isEnabled('new-payment-gateway')) {
    return newPaymentGateway.process(order)
  }
  return legacyPaymentGateway.process(order)
}

One deployment. Two code paths. You choose which runs.

Four types of feature flags

Release Toggles: Gradual rollouts of new features (short-lived, weeks)

Experiment Toggles: A/B tests and multivariate testing (duration of test)

Ops Toggles: Kill switches during outages (permanent circuit breakers)

Permission Toggles: Premium features, access control (permanent)

Architecture: Simple flag system

// Flag evaluation service
class FeatureFlagService {
  private flags: Map<string, boolean>

  async isEnabled(flagName: string, userId?: string): boolean {
    const config = await this.getConfig(flagName)
    return this.evaluate(config, userId)
  }

  private evaluate(config: FlagConfig, userId?: string): boolean {
    // Percentage rollout
    if (config.percentage < 100) {
      return this.hash(userId) < config.percentage
    }
    return config.enabled
  }
}

Gradual rollouts: The safe way

Start small. Monitor. Expand.

Day 1: 1% of users (internal team) Day 2: 5% of users (early adopters) Day 4: 25% of users (canary release) Day 7: 50% of users (half production) Day 10: 100% of users (full release)

Problem at any stage? Flip the flag to 0% instantly.

Canary releases with flags

Named after canaries in coal mines—they detect danger early.

// Route 5% to new service, 95% to legacy
const flagConfig = {
  name: 'new-checkout-flow',
  percentage: 5,
  enabled: true,
  monitoringMetrics: ['error_rate', 'latency', 'conversion']
}

If error rate spikes with the 5%, kill the flag before it hits everyone.

User targeting and cohorts

Not just percentages. Target specific user groups.

const flagRules = {
  name: 'ai-recommendations',
  rules: [
    { segment: 'internal', enabled: true },
    { segment: 'premium-users', enabled: true },
    { segment: 'beta-testers', enabled: true },
    { segment: 'region:us-west', percentage: 25 },
    { default: false }
  ]
}

Roll out by: team, tier, region, device, behavior.

Code example: Simple routing

// Legacy modernization with feature flags
export class OrderProcessor {
  async processOrder(order: Order) {
    const useNewService = await flags.isEnabled(
      'modern-order-service',
      order.customerId
    )

    if (useNewService) {
      return this.modernService.process(order)
    }

    // Fallback to legacy
    return this.legacyService.process(order)
  }
}

Code example: Advanced routing

// Gradual migration with monitoring
export class PaymentRouter {
  async routePayment(payment: Payment) {
    const config = await flags.getConfig('new-payment-api')

    // Check eligibility first
    if (!this.isEligible(payment, config)) {
      return this.legacy.process(payment)
    }

    try {
      const result = await this.modern.process(payment)
      this.metrics.recordSuccess('modern')
      return result
    } catch (error) {
      this.metrics.recordFailure('modern', error)
      // Auto-fallback to legacy
      return this.legacy.process(payment)
    }
  }
}

Real-world: Etsy's continuous deployment

Etsy deploys 50+ times per day using feature flags.

Their approach:

  • Every feature behind a flag
  • Deploy dark (code shipped, flag OFF)
  • Enable for employees first
  • Gradual rollout to 100%
  • Remove flag after full rollout

Result: Confidence to ship continuously without fear.

Flag lifecycle: Avoiding flag debt

Flags left in code become technical debt.

Lifecycle stages:

  1. Created (new feature development)
  2. Enabled (gradual rollout)
  3. Fully rolled out (100% of users)
  4. Cleaned up (code removed, flag deleted)

Set expiration dates when you create flags. 30-90 days max for release toggles.

Best practices and pitfalls

DO:

  • Set expiration dates on release flags
  • Monitor flag evaluation performance
  • Test all flag states (on/off)
  • Keep flags focused and small
  • Schedule regular cleanup days

DON'T:

  • Nest flags inside flags (complexity explosion)
  • Keep release flags alive for months
  • Use flags for everything
  • Skip testing the "off" state

When to use (and when not to)

Use feature flags when:

  • Rolling out risky changes
  • Need instant kill switch
  • A/B testing required
  • Gradual rollout desired
  • Enabling modernization patterns

Don't use flags for:

  • Configuration (use config files)
  • Long-lived business logic
  • Every small change (overhead adds up)

How flags enable modernization patterns

Strangler Fig: Route traffic between old and new systems

if (flags.isEnabled('new-customer-api')) {
  return newSystem.getCustomer(id)
}
return legacySystem.getCustomer(id)

Branch by Abstraction: Switch implementations behind abstraction

const implementation = flags.isEnabled('new-db')
  ? newDatabase : legacyDatabase

Parallel Run: Control traffic split for dual writes

if (flags.percentage('dual-write') > random()) {
  await Promise.all([old.write(), new.write()])
}

Key takeaways

  1. Decouple deployment from release – ship code safely, release confidently

  2. Gradual rollouts reduce risk – 1% → 5% → 25% → 50% → 100%

  3. Flags are temporary – clean up or they become tech debt

Feature flags turn deployment from a scary event into a non-event.

Implementation checklist

Week 1: Foundation

  • Choose flag system (LaunchDarkly, Unleash, or build simple service)
  • Implement basic flag evaluation
  • Add monitoring and metrics

Week 2: First flags

  • Wrap one risky feature in a flag
  • Test both states (on/off)
  • Practice gradual rollout

Week 3: Process

  • Set flag naming conventions
  • Define cleanup policy (30-90 day expiry)
  • Schedule monthly flag cleanup reviews

Start small. Build confidence. Scale gradually.

1 / 0
Feature Flags for Legacy Systems