Configuration and Feature Flags
The 3-Line Engineering Rule
Decouple code deployment from feature release using dynamic feature flags with automated kill switches.
Deploying new code straight to 100% of users without flags means any bug requires an emergency rollback, re-building images, and re-deploying.
Use percentage-based canary rollouts (1% -> 5% -> 25% -> 100%), test in production with dark launches, and instrument emergency kill switches that update in < 5 seconds without redeploying.
Architectural Blueprint & Sequence
Hover over nodes to inspect state transitionsUser Request
User ID: 94821
Flag Evaluator
hash(userId + flagKey) % 100
New V2 Algorithm
Bucket < 10% (Canary)
Legacy V1 Algorithm
Bucket >= 10% (Stable)
Emergency Kill Switch
Instant OFF in < 2s without deploy
Key Engineering Takeaways
- Deploy code dark (feature disabled) to test database queries and background load first.
- Roll out incrementally by user ID hash to ensure consistent user experience.
- Always design a fallback path when the flag is toggled OFF (kill switch).
- Delete obsolete feature flags within 30 days of 100% rollout to prevent tech debt.
The Problem & Production Reality
Continuous Delivery relies on small, frequent deployments. Feature flags separate the risk of deploying code from the risk of turning on business features. By dark launching features and routing 1% of traffic through them, engineers detect performance regressions before users notice.
Anti-Pattern: Big Bang Releases Without Rollback Toggles
Flipping a massive new checkout engine to 100% of global users at midnight with no kill switch.
// ❌ ANTI-PATTERN: All or nothing
public CheckoutResponse checkout(Order order) {
// If new checkout has a hidden deadlock, rolling back takes 30 minutes of CI/CD!
return newCheckoutEngineV2.execute(order);
}Production Pattern: Deterministic Canary Evaluation with Fallback
Evaluate user cohorts deterministically based on user ID hashing, backed by an in-memory configuration provider.
// ✅ PRODUCTION PATTERN: Deterministic Canary Evaluation
@Service
public class CheckoutService {
private final FeatureFlagService flagService;
private final CheckoutEngineV1 legacyEngine;
private final CheckoutEngineV2 newEngine;
public CheckoutResponse checkout(User user, Order order) {
// Flag evaluated in-memory (< 1ms)
boolean isV2Enabled = flagService.isFeatureEnabled(
"new-checkout-v2",
user.getId(),
false // Default fallback if flag provider unreachable
);
if (isV2Enabled) {
try {
return newEngine.execute(order);
} catch (Exception ex) {
log.error("Canary V2 failed for user={}. Activating local fallback.", user.getId(), ex);
metrics.increment("checkout.v2.error");
return legacyEngine.execute(order); // Safe local fallback
}
}
return legacyEngine.execute(order);
}
}Trade-off Analysis & Staff Recommendations
| Aspect | Pros / Strengths | Cons / Hazards | Staff Recommendation |
|---|---|---|---|
| In-Memory Polling vs Real-Time WebSocket Flags | In-memory caching evaluates in nanoseconds without network latency on critical paths. | Takes 1-5 seconds for flag changes to propagate to all pods. | Use local in-memory evaluation with background polling (e.g. Unleash / LaunchDarkly SDK). |
| Flag Tech Debt | Total safety during rollout. | Dozens of nested `if/else` flag checks make code unreadable over time. | Treat flag removal as a mandatory sprint ticket as soon as a feature reaches 100% stable rollout. |