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.
Useful resources
Or clone locally
git clone -b challenge/2026-09-08 https://github.com/niltonheck/typedrop.git