Deploy on Friday. Release on Monday. That’s the power of feature flags.

The traditional model couples deployment with release—code goes to production, users see it immediately. Feature flags break that coupling, letting you deploy dark code and control visibility separately from deployment.

The Core Pattern

A feature flag is a conditional that wraps functionality:

1
2
3
4
5
if (featureFlags.isEnabled('new-checkout-flow', { userId: user.id })) {
  return renderNewCheckout();
} else {
  return renderLegacyCheckout();
}

Simple in concept. Transformative in practice.

Why This Matters

1. Deployment becomes low-risk

Your CI/CD pipeline can deploy to production multiple times per day. New features ship behind flags, invisible to users. If something breaks, you haven’t exposed anyone to it.

2. Instant rollback without rollback

Production incident? Toggle the flag off. No redeployment, no rollback procedures, no waiting for CI. The old code is still there—you just show it again.

3. Progressive rollout

Instead of all-or-nothing releases:

  • Enable for internal users first
  • Expand to 1% of traffic
  • Watch metrics, expand to 10%
  • Full rollout when confident

4. A/B testing built-in

Feature flags are the foundation for experimentation. Route users to variants, measure outcomes, make data-driven decisions.

Implementation Approaches

Simple: Environment Variables

Good enough for small teams:

1
2
3
4
const FEATURES = {
  newDashboard: process.env.FEATURE_NEW_DASHBOARD === 'true',
  darkMode: process.env.FEATURE_DARK_MODE === 'true',
};

Pros: No dependencies, no latency, no cost. Cons: Requires redeployment to change. No targeting. No gradual rollout.

Better: Configuration Service

Store flags in a database or config service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class FeatureFlagService {
  constructor(configStore) {
    this.configStore = configStore;
    this.cache = new Map();
  }

  async isEnabled(flagName, context = {}) {
    let flag = this.cache.get(flagName);
    if (!flag) {
      flag = await this.configStore.getFlag(flagName);
      this.cache.set(flagName, flag);
    }
    return this.evaluate(flag, context);
  }

  evaluate(flag, context) {
    if (!flag.enabled) return false;
    if (flag.percentage && Math.random() * 100 > flag.percentage) return false;
    if (flag.allowlist?.includes(context.userId)) return true;
    if (flag.rules) return this.evaluateRules(flag.rules, context);
    return flag.enabled;
  }
}

Pros: Runtime changes without deployment. Targeting capability. Cons: Latency (mitigate with caching). Need to build and maintain.

Production-Grade: Dedicated Platform

LaunchDarkly, Split, Flagsmith, Unleash—purpose-built platforms handle:

  • Edge-cached evaluation (millisecond latency)
  • Sophisticated targeting rules
  • Audit logs
  • Scheduled rollouts
  • Kill switches

Worth the cost for high-stakes deployments.

Targeting Strategies

Flags become powerful with targeting rules:

1
2
3
4
5
6
7
8
flag: new-pricing-page
rules:
  - if: user.plan == "enterprise"
    percentage: 100  # All enterprise users
  - if: user.country in ["US", "CA"]
    percentage: 25   # 25% of North American users
  - default:
    percentage: 0    # Everyone else: off

Common targeting dimensions:

  • User attributes (plan, tenure, role)
  • Geographic (country, region)
  • Technical (browser, platform, app version)
  • Random percentage (canary releases)
  • Time-based (scheduled launches)

The Progressive Delivery Pipeline

Combine flags with observability for safe releases:

1234567.......DICMEFCenaoxulptnnpleleaialaorrtnnynyodrarou(l(olpc1erlod-rofdo5rruleg%ootafrlgioolnofrb(daarpiutcernsekmogesodr,bvu(saecf)lstlaefiatdrogeonnom,ocnnycf,molfedaoctegror)nioevcfmesfpr)lsoiyoenes)

Automate the expand/rollback decision:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
async function evaluateCanary(flagName, metrics) {
  const baseline = await getBaselineMetrics();
  const canary = await getCanaryMetrics(flagName);
  
  if (canary.errorRate > baseline.errorRate * 1.1) {
    await flagService.disable(flagName);
    await alert(`Canary ${flagName} rolled back: error rate elevated`);
    return 'rollback';
  }
  
  if (canary.p99Latency > baseline.p99Latency * 1.2) {
    await flagService.disable(flagName);
    await alert(`Canary ${flagName} rolled back: latency regression`);
    return 'rollback';
  }
  
  return 'healthy';
}

Flag Hygiene

Flags accumulate. Old flags become technical debt—dead code paths, confusing conditionals, test complexity.

Practices that help:

  1. Expiration dates: Every flag gets a removal deadline when created
  2. Ownership: Assign an owner responsible for cleanup
  3. Automated detection: Lint rules that flag old flags
  4. Regular audits: Monthly review of active flags
1
2
3
4
5
6
7
8
// Flag with metadata
const flag = {
  name: 'new-search-algorithm',
  owner: 'search-team',
  createdAt: '2026-01-15',
  removeBy: '2026-03-15',  // Must clean up by this date
  status: 'ramping',       // created | ramping | stable | deprecated
};

Anti-Patterns

Flag spaghetti: Nesting flags inside flags, creating combinatorial complexity. If you need if (flagA && !flagB || flagC), refactor.

Permanent flags: Some flags never get removed. They’re not feature flags—they’re configuration. Treat them differently.

Testing all combinations: With 10 binary flags, you have 1,024 possible states. Don’t test all combinations—test the paths that matter.

Flag-driven architecture: If your entire application flow depends on flag states, you’ve built a mess. Flags should wrap features, not define architecture.

The Mental Model

Think of feature flags as giving you a remote control for production:

  • Play: Enable the feature
  • Pause: Disable while you investigate
  • Rewind: Roll back to old behavior
  • Fast-forward: Skip the queue, enable for specific users

The code is already deployed. You’re just deciding who sees what, when.

That’s the power: deployment becomes a non-event. Release becomes a decision you can make, change, and reverse without touching your CI/CD pipeline.

Deploy on Friday. Watch the metrics over the weekend. Release on Monday. Sleep well.