~/shivanshshekhar11.tech — boot
shivansh@portfolio:~$
All writing
DISTRIBUTED SYSTEMS11 min read

Feature flags are a distributed systems problem

SS
Shivansh Shekhar
Apr 14, 2026

I built Pulse because every feature flag service I evaluated treated flag evaluation as a simple key-value lookup. "Is this flag on? Yes or no." In practice, the interesting question is never whether a flag is on. It's for whom, under what conditions, and how stale is the answer allowed to be.

Once you accept that framing, feature flags stop being a frontend concern and become a distributed systems problem. Here's what I learned building one from scratch.

The rule engine nobody thinks about

Most flag services support simple targeting: "enable for 10% of users" or "enable for users in the beta group." Pulse needed something more expressive because our tenants had complex rollout conditions: "enable for enterprise accounts in the US that have used the API in the last 30 days and are not on a trial plan."

Building this as a chain of if-else statements would work for five rules. It collapses at fifty. Instead, I wrote a recursive rule engine — a small AST evaluator that composes conditions using AND/OR/NOT operators, each leaf being a typed comparator (string equality, numeric range, set membership, regex match).

rule-engine.ts
type Rule =
  | { op: "and" | "or"; rules: Rule[] }
  | { op: "not"; rule: Rule }
  | { op: "eq" | "gt" | "in" | "re";
    field: string; value: unknown };

function evaluate(rule: Rule, ctx: Record<string, unknown>): boolean {
  switch (rule.op) {
    case "and": return rule.rules.every(r => evaluate(r, ctx));
    case "or":  return rule.rules.some(r => evaluate(r, ctx));
    case "not": return !evaluate(rule.rule, ctx);
    case "eq":  return ctx[rule.field] === rule.value;
    case "gt":  return (ctx[rule.field] as number) > (rule.value as number);
    case "in":  return (rule.value as unknown[]).includes(ctx[rule.field]);
    case "re":  return new RegExp(rule.value as string).test(ctx[rule.field] as string);
  }
}

Stale flags are silent bugs

The subtle failure mode in feature flags is staleness. A client SDK caches the flag state locally (it has to — you can't make a network call on every render). But when a flag changes on the server, how fast does the client learn about it?

Pulse uses Server-Sent Events (SSE) to push flag changes to connected clients in real time. When a flag rule is updated on the server, every connected client receives the delta within milliseconds, not seconds. It's not a novel architecture — it's the same pattern behind every real-time collaboration tool — but I'm consistently surprised by how many flag services skip it.

"The gap between 'flag was changed' and 'all clients reflect the change' is where rollback failures live. SSE closes that gap from seconds to milliseconds without the complexity of WebSockets."

Split-brain rollouts

In a multi-instance deployment, flag updates need to propagate across all server instances. If instance A receives the update but instance B doesn't, you've split your user base into two inconsistent populations without knowing it. Pulse uses Redis Pub/Sub as the inter-instance broadcast layer — source of truth in Postgres, notification bus in Redis.

Percentage rollouts are harder than they look

"Roll this out to 10% of users" sounds trivial. The percentage has to be sticky — user A should consistently see the same flag state, not flip randomly on every evaluation. The solution is to hash the user ID with the flag key and check if the hash falls in the target percentile range. Getting this wrong means users lose access to features they were already using — the kind of bug that generates support tickets but doesn't crash anything.

The lesson

Feature flags look like configuration. They're actually distributed state. The flag service is the source of truth, the SDK cache is a replica, and the gap between them is a consistency window. Once you see it through that lens, the design decisions — SSE, Redis Pub/Sub, deterministic hashing — follow naturally.

#feature-flags#distributed#sse#rule-engine#consistency
SS
Shivansh Shekhar

Backend & AI Systems Engineer. Writes about distributed primitives, LLM infrastructure, and the small decisions that quietly compound.