Feature Flags
How to implement, operate, and retire feature flags.
Flag Lifecycle
| Phase | Duration | Actions |
|---|---|---|
| Create | Day 0 | Add flag, deploy code OFF |
| Enable | Days 1-14 | Gradual rollout (10% → 50% → 100%) |
| Stabilize | Days 15-30 | Monitor, collect feedback |
| Remove | Day 30-90 | Delete flag and old code paths |
Creating a Flag
Step 1: Define the Flag
# flags.yaml
features:
new-checkout:
description: "New checkout flow with one-click purchase"
owner: "checkout-team"
created: "2024-01-15"
cleanup_deadline: "2024-04-15"
enabled: false
Step 2: Implement in Code
func processCheckout(user User) {
if featureFlags.IsEnabled("new-checkout", user) {
newCheckoutFlow(user)
} else {
legacyCheckoutFlow(user)
}
}
Step 3: Deploy
Deploy with flag OFF. Code reaches production but feature is not active.
Enabling a Flag (Rollout)
Simple Boolean
Percentage Rollout
Increase gradually:
| Day | Percentage | Validation |
|---|---|---|
| 1 | 10% | Monitor errors |
| 3 | 25% | Check business metrics |
| 7 | 50% | Validate performance |
| 14 | 100% | Full rollout |
User-Targeted Rollout
new-checkout:
enabled_for_users:
- "beta@example.com"
- "tester@example.com"
enabled_for_groups:
- "beta-testers"
- "internal-users"
Time-Based Flag
Monitoring Flags
Track key metrics per flag:
| Metric | Flag OFF | Flag ON | Action |
|---|---|---|---|
| Error rate | 0.1% | 0.5% | Investigate |
| Latency P95 | 100ms | 150ms | Acceptable |
| Conversion | 2.5% | 2.8% | Success! |
Emergency Disable
When issues occur:
# Instant disable
curl -X PUT https://flags.example.com/api/flags/new-checkout \
-H "Authorization: Bearer $TOKEN" \
-d '{"enabled": false}'
Or through UI dashboard.
Removing Flags
When to Remove
- Feature stable for 30+ days
- 100% rollout complete
- No issues or rollbacks needed
- Business metrics validated
Cleanup Steps
- Verify stability - No issues for 30 days
- Remove old code path - Delete legacy implementation
- Remove flag checks - Delete conditional logic
- Remove flag definition - Delete from configuration
- Deploy cleanup - Single commit removing all traces
Example Cleanup Commit
chore(checkout): remove new-checkout feature flag
Feature has been stable at 100% for 45 days.
Removing flag and legacy checkout code.
- Remove feature flag checks from checkout.go
- Delete legacy_checkout.go
- Remove flag from flags.yaml
- Update tests
Relates to #456
Flag Hygiene
Track Flag Age
| Flag | Created | Age | Status |
|---|---|---|---|
| new-checkout | 2024-01-15 | 45 days | Ready for cleanup |
| beta-dashboard | 2024-02-01 | 30 days | Monitoring |
| experiment-a | 2023-10-01 | 120 days | OVERDUE |
Enforce Cleanup Deadlines
Alert when deadline approaches or passes.
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Nested flags | Complex logic, hard to test | Flatten or combine |
| Permanent flags | Technical debt accumulates | Enforce cleanup deadlines |
| Missing owners | Nobody responsible | Require owner in definition |
| No monitoring | Can't detect issues | Add metrics per flag |
Next Steps
- Release Toggling - Control feature exposure at runtime
- Deployment Rings - Progressive rollout to user groups
- Rollback Procedures - Emergency response procedures
- Deployment Strategies - Decoupling deployment from release
Tutorials | How-to Guides | Explanation | Reference
You are here: Explanation — understanding-oriented discussion that clarifies concepts.