feature-flag-ops

Manages the full lifecycle of feature flags - creation, progressive rollout, kill switches, percentage ramps, user targeting, and cleanup. Use when releasing features behind flags, managing rollouts, or cleaning up stale flags.

Feature Flag Operations

Feature flags decouple deployment from release. Deploy code any time. Release features when ready. Roll back without redeploying.

Flag Lifecycle

CREATE → TEST → RAMP → FULL ROLLOUT → CLEANUP

Every flag should go through this lifecycle. Flags that skip CLEANUP become permanent tech debt.

1. Create

FLAG NAME: enable_new_checkout
TYPE: release (temporary) / operational (permanent) / experiment (A/B test)
DEFAULT: false (OFF)
OWNER: [who's responsible for this flag]
CLEANUP DATE: [date to remove the flag - set it NOW]

2. Test

  • Verify: feature works with flag ON
  • Verify: existing behavior works with flag OFF
  • Both paths are tested in CI

3. Ramp (Progressive Rollout)

Day 1: 1% of users (canary - catch crashes)
Day 2: 5% of users (small sample - check metrics)
Day 3: 25% of users (moderate - verify at scale)
Day 5: 50% of users (large - final validation)
Day 7: 100% of users (full rollout)

At each stage:

  • Monitor error rate (should not increase)
  • Monitor latency (should not degrade)
  • Monitor business metrics (should not drop)
  • Check support tickets (no new complaints)

4. Full Rollout

Flag is ON for 100% of users. Feature is fully released.

5. Cleanup (Critical - Don't Skip)

1. Remove the flag check from code (always return the "ON" path)
2. Remove the "OFF" code path entirely
3. Delete the flag from your flag management system
4. Merge cleanup PR

Set a cleanup date at creation time. If the flag is still alive 30 days after full rollout, it's tech debt.

Flag Types

TypeLifetimeExampleCleanup
Release flagDays to weeksNew checkout flowRemove after full rollout
Operational flagPermanentMaintenance mode, rate limit bypassKeep (but review annually)
Experiment flagWeeksA/B test: button colorRemove after experiment concludes
Permission flagPermanentPremium features, beta accessKeep (tied to entitlements)

Most flags should be release flags. If you have > 10 permanent flags, audit them.

Kill Switch

Every feature behind a flag has an instant rollback mechanism.

INCIDENT: New checkout causing payment failures
ACTION: Toggle enable_new_checkout → OFF
EFFECT: All users immediately get old checkout
TIME: < 1 minute (no deployment needed)

Kill Switch Checklist

  • Flag can be toggled in < 60 seconds (UI or CLI)
  • Toggling OFF immediately disables the feature (no cache delay)
  • On-call knows how to toggle flags
  • Runbook documents which flags to kill for which symptoms

Targeting

Percentage Rollout

enable_new_checkout:
  percentage: 25    # 25% of users see new checkout
  sticky: true      # Same user always gets same experience

Sticky randomization: Hash(user_id + flag_name) determines bucket. Same user always gets the same result.

User Targeting

enable_new_checkout:
  users:
    - [email protected]    # Always ON for internal testing
    - [email protected]     # Specific beta users
  percentage: 0               # OFF for everyone else

Attribute Targeting

enable_new_checkout:
  rules:
    - if country IN ["US", "CA"]: percentage 50
    - if plan == "enterprise": ON
    - default: OFF

Code Patterns

Simple Flag Check

# GOOD: Clean branching
if feature_flags.is_enabled("new_checkout", user):
    return new_checkout_flow(cart)
else:
    return old_checkout_flow(cart)

Don't Nest Flags

# BAD: Combinatorial explosion
if flag_a and flag_b:
    # path 1
elif flag_a and not flag_b:
    # path 2
elif not flag_a and flag_b:
    # path 3
else:
    # path 4
# 4 code paths = 4x testing burden

Keep Flag Scope Small

# BAD: Flag wraps 500 lines of code
if feature_flags.is_enabled("new_flow"):
    # ... 500 lines ...
else:
    # ... 500 lines ...

# GOOD: Flag selects a strategy, both paths share most code
processor = NewProcessor() if feature_flags.is_enabled("new_flow") else OldProcessor()
result = processor.handle(request)  # Same interface

Monitoring Flags

MetricAlert
Total active flagsAlert if > threshold (flag sprawl)
Flag ageAlert if release flag > 30 days old (stale)
Flag without ownerAlert (orphaned flag)
Error rate per flag variantAlert if ON variant has higher errors
Rollout percentage changesAudit log (who changed what, when)

Flag Hygiene

Monthly Flag Review

For each active flag:
  - Is it still needed?
    → Release flag at 100%? CLEAN UP
    → Experiment concluded? CLEAN UP
    → Operational flag? Review if still relevant
  - Does it have an owner?
    → No owner? Assign one or clean up
  - Is it tested?
    → Both ON and OFF paths covered in tests?

Maximum Active Flags

Set a team limit (e.g., max 15 active flags). When you hit the limit, clean up before creating new ones. This prevents flag sprawl.

Anti-Patterns

Anti-PatternProblemFix
Flags never cleaned upCode full of dead branches, combinatorial complexitySet cleanup dates, enforce in reviews
Nested/dependent flagsUntestable combinationsOne flag per feature, no nesting
Flag as permanent configBypasses proper config managementUse config for config, flags for rollout
No monitoring per variantCan't tell if new path is brokenMonitor metrics split by flag variant
Manual percentage decisionsInconsistent, riskyFollow standard ramp schedule
Testing only ON pathOFF path breaks, rollback failsTest both paths in CI