TypeDrop

2026-09-08 Challenge

2026-09-08 Medium

Typed Feature-Flag Evaluation Engine

You're building the feature-flag evaluation core for a SaaS platform. Flags can be simple on/off toggles, percentage rollouts, or user-segment overrides; the evaluator must resolve the correct variant for a given user context and produce a typed audit record — all without a single `any` or unsafe cast.

Goals

  • Define a three-member discriminated union `FlagRule` and a generic `Flag<V extends string>` type with a template-literal branded key.
  • Implement `evaluateFlag` so it iterates rules in order, narrows each rule by its `kind` discriminant, and returns exactly `V` (not `string`).
  • Implement `evaluateWithAudit` to produce a fully typed `EvaluationRecord<V>` that captures which rule kind fired (or `"default"`).
  • Implement `batchEvaluate` to evaluate a list of flags and return a `Map` keyed by the flag's branded `FlagKey`.
challenge.ts
// Key types & main function signatures at a glance

type Segment = "beta" | "internal" | "standard" | "enterprise";

export type FlagKey = `flag_${string}`;

export type FlagRule =
  | { kind: "boolean"; value: boolean }
  | { kind: "percentage"; threshold: number }
  | { kind: "segment"; segmentVariants: Record<Segment, V | null>; defaultVariant: V };
  // (V must come from the enclosing Flag<V> — see full challenge for context)

export type Flag<V extends string> = {
  key: FlagKey;
  rules: FlagRule[];          // first match wins
  defaultVariant: V;
};

export type UserContext = {
  userId: string;
  segment: Segment;
  attributes: Record<string, string | number | boolean>;
};

export function evaluateFlag<V extends string>(
  flag: Flag<V>,
  user: UserContext
): V { /* TODO */ }

export function evaluateWithAudit<V extends string>(
  flag: Flag<V>,
  user: UserContext
): EvaluationRecord<V> { /* TODO */ }
Hints (click to reveal)

Hints

  • The `segment` rule's `segmentVariants` field must use `Record<UserContext["segment"], V | null>` — a mapped type over the segment union — so the compiler enforces all four segments are handled.
  • Use a `switch (rule.kind)` block inside `evaluateFlag`; TypeScript will narrow the rule type in each branch, giving you safe access to `rule.value`, `rule.threshold`, etc.
  • To preserve the literal type `V` through `evaluateWithAudit`, call `evaluateFlag` internally and annotate the return as `EvaluationRecord<V>` — don't widen to `string` at any intermediate step.

Or clone locally

git clone -b challenge/2026-09-08 https://github.com/niltonheck/typedrop.git