Feature Flags im großen Maßstab
Feature Flags entkoppeln Deployment und Release — so setzt du sie um, ohne ein unentwirrbares Geflecht aus bedingter Logik zu erzeugen.

Feature Flags sind eines der mächtigsten Werkzeuge in der modernen Softwarebereitstellung. Sie erlauben es dir, unvollständige Features in Produktion zu deployen, A/B-Tests durchzuführen, Änderungen schrittweise auszurollen und problematische Features sofort abzuschalten — ohne neu zu deployen. Aber ohne Disziplin werden Flags zu technischer Schuld, die jeden Codepfad schwerer nachvollziehbar macht.
Die einfachste Implementierung
Im Kern ist ein Feature Flag eine Bedingung. Die Frage ist, woher der Flag-Wert kommt und wie er verwaltet wird.
// ❌ Hard-coded flag — requires redeployment to change
const NEW_CHECKOUT = true;
if (NEW_CHECKOUT) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}
// ✅ Runtime flag — changeable without deployment
const flags = await getFeatureFlags(userId);
if (flags.isEnabled("new-checkout")) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}Runtime-Flags sind das Ziel. Sie lassen sich in Sekunden über ein Dashboard umschalten, verglichen mit Minuten oder Stunden für einen Deployment.
Flag-Typen
Nicht alle Flags dienen demselben Zweck. Ihre Kategorisierung bestimmt Lebenszyklus und Aufräumstrategie.
| Typ | Zweck | Lebensdauer | Beispiel |
|---|---|---|---|
| Release-Flag | Unvollständige Features gatten | Tage bis Wochen | new-checkout |
| Experiment-Flag | A/B-Tests | Wochen bis Monate | checkout-redesign-v2 |
| Ops-Flag | Kill Switch für Last | Dauerhaft | disable-search-indexing |
| Berechtigungs-Flag | Features nach Nutzerstufe | Dauerhaft | premium-analytics |
Release-Flags sollten kurzlebig sein und aggressiv aufgeräumt werden. Experiment-Flags leben während der Testphase. Ops- und Berechtigungs-Flags sind dauerhafte Infrastruktur.
Einen Flag-Service bauen
interface FeatureFlag {
key: string;
enabled: boolean;
rolloutPercentage: number;
allowedUserIds: string[];
rules: FlagRule[];
}
interface FlagRule {
attribute: string;
operator: "eq" | "in" | "gt" | "lt";
value: unknown;
}
class FeatureFlagService {
private flags: Map<string, FeatureFlag>;
constructor(flags: FeatureFlag[]) {
this.flags = new Map(flags.map((f) => [f.key, f]));
}
isEnabled(key: string, context: FlagContext): boolean {
const flag = this.flags.get(key);
if (!flag) return false;
if (!flag.enabled) return false;
// Explicit allow list
if (flag.allowedUserIds.includes(context.userId)) return true;
// Rule-based targeting
if (flag.rules.length > 0) {
return flag.rules.every((rule) => this.evaluateRule(rule, context));
}
// Percentage rollout — deterministic per user
if (flag.rolloutPercentage < 100) {
const hash = this.hashUserForFlag(context.userId, key);
return hash < flag.rolloutPercentage;
}
return true;
}
private hashUserForFlag(userId: string, flagKey: string): number {
// Deterministic hash — same user always gets same result
let hash = 0;
const input = `${userId}:${flagKey}`;
for (let i = 0; i < input.length; i++) {
hash = (hash * 31 + input.charCodeAt(i)) | 0;
}
return Math.abs(hash) % 100;
}
private evaluateRule(rule: FlagRule, context: FlagContext): boolean {
const value = context[rule.attribute];
switch (rule.operator) {
case "eq": return value === rule.value;
case "in": return (rule.value as unknown[]).includes(value);
case "gt": return (value as number) > (rule.value as number);
case "lt": return (value as number) < (rule.value as number);
default: return false;
}
}
}Das hashbasierte prozentuale Rollout ist entscheidend: Es stellt sicher, dass ein Nutzer konsistent dieselbe Variante sieht, ohne Zuordnungen zu speichern. Erhöhe den Prozentsatz, um schrittweise mehr Nutzer freizuschalten.
Schrittweise Rollouts
Der sicherste Weg, ein Feature zu releasen, ist schrittweise: zuerst interne Nutzer, dann ein kleiner Prozentsatz, dann weiter.
// Rollout strategy for a major feature
const rolloutStages = [
{ percentage: 0, allowList: ["internal-team-ids"], duration: "2 days" },
{ percentage: 5, allowList: [], duration: "3 days" },
{ percentage: 25, allowList: [], duration: "2 days" },
{ percentage: 50, allowList: [], duration: "2 days" },
{ percentage: 100, allowList: [], duration: "permanent" },
];// Monitor error rates at each stage
function shouldProceedToNextStage(metrics: StageMetrics): boolean {
const errorRateThreshold = 0.01; // 1%
const latencyP99Threshold = 500; // ms
return (
metrics.errorRate < errorRateThreshold &&
metrics.latencyP99 < latencyP99Threshold &&
metrics.userComplaints === 0
);
}Wenn die Fehlerrate bei 5% hochschießt, rollst du sofort auf 0% zurück — kein Deployment, kein Downtime.
Das Aufräum-Problem
Das größte Risiko bei Feature Flags ist nicht das Feature, sondern die Flags selbst. Veraltete Flags sammeln sich an und erzeugen eine nicht testbare kombinatorische Explosion.
// ❌ Stale flags — nobody knows if these are still needed
if (flags.isEnabled("new-checkout")) {
if (flags.isEnabled("checkout-v2-variant-b")) {
if (flags.isEnabled("express-checkout")) {
// How many code paths is this? 2^3 = 8 combinations to test
}
}
}
// ✅ Clean up flags after full rollout
// 1. Roll out to 100%
// 2. Monitor for 1 week
// 3. Remove the flag check, keep only the new code path
// 4. Delete the flag from the configurationFüge Release-Flags Ablaufdaten hinzu. Verfolge das Alter von Flags in deinem Dashboard. Ein Release-Flag, das älter als 30 Tage ist, ist technische Schuld.
Testen mit Flags
Feature Flags erschweren das Testen. Du musst beide Pfade testen — und idealerweise den Übergang zwischen ihnen.
describe("checkout", () => {
it("renders new checkout when flag is enabled", () => {
const flags = new FeatureFlagService([
{ key: "new-checkout", enabled: true, rolloutPercentage: 100, allowedUserIds: [], rules: [] },
]);
const result = renderCheckout(flags, testContext);
expect(result).toContain("Express Checkout");
});
it("renders legacy checkout when flag is disabled", () => {
const flags = new FeatureFlagService([
{ key: "new-checkout", enabled: false, rolloutPercentage: 0, allowedUserIds: [], rules: [] },
]);
const result = renderCheckout(flags, testContext);
expect(result).toContain("Standard Checkout");
});
});Wichtige Erkenntnisse
- Feature Flags entkoppeln Deployment und Release — deploye täglich, release, wenn es bereit ist
- Kategorisiere Flags nach Typ — Release-Flags sind temporär, Ops-Flags sind dauerhaft
- Schrittweise Rollouts reduzieren das Risiko — starte bei 0%, erhöhe basierend auf Metriken
- Hashbasiertes prozentuales Rollout stellt sicher, dass Nutzer konsistent dieselbe Variante sehen
- Räume Flags aggressiv auf — veraltete Flags erzeugen nicht testbare kombinatorische Explosionen
- Jedes Flag braucht einen Eigner und ein Ablaufdatum für Release-Flags


