TypeDrop
2026-04-14 Challenge
2026-04-14
Medium
Typed Feature Flag Evaluator
You're building the feature-flag evaluation engine for a product experimentation platform. Raw flag configurations arrive from a remote config service as unknown JSON; your engine must validate them, evaluate each flag against a typed user context, and return a strongly-typed rollout report — with zero `any`.
Goals
- Implement `isAudience`, `isOperator`, and `isVariantKind` as proper type-guard functions that narrow `unknown` to their respective union types.
- Implement `parseFlagVariant`, `parseTargetingRule`, and `parseFeatureFlag` to safely validate raw `unknown` JSON into typed domain objects, throwing descriptive errors on bad input.
- Implement `evaluateRule` to correctly evaluate all six operators (`eq`, `neq`, `in`, `not_in`, `gte`, `lte`) against a `UserContext`.
- Implement `evaluateFlags` to orchestrate validation, audience gating, rule matching, and aggregate the results into a fully typed `EvaluationReport` with accurate summary counts.
challenge.ts
// Core domain types + main function signature
type Audience = "internal" | "beta" | "public";
type Operator = "eq" | "neq" | "in" | "not_in" | "gte" | "lte";
interface UserContext {
userId: string; email: string; plan: string;
country: string; accountAgeDays: number; audience: Audience;
}
type FlagVariant =
| { kind: "boolean"; enabled: boolean }
| { kind: "string"; variant: string }
| { kind: "number"; variant: number };
type FlagResult =
| { status: "matched"; flagId: string; rule: TargetingRule; variant: FlagVariant }
| { status: "default"; flagId: string; variant: FlagVariant }
| { status: "invalid"; flagId: string; error: string };
interface EvaluationReport {
userId: string; evaluatedAt: string;
results: FlagResult[];
summary: { total: number; matched: number; defaulted: number; invalid: number };
}
// Entry point — validate raw blobs, evaluate rules, return typed report
function evaluateFlags(rawFlags: unknown[], user: UserContext): EvaluationReport { ... }
Hints (click to reveal)
Hints
- Use an `as const` array (e.g. `const VALID_AUDIENCES = [...] as const`) combined with `.includes()` to implement membership-check type guards without `any` — you may need a small cast on the `includes` call since TS widens the array type; consider `(arr as readonly unknown[]).includes(value)`.
- In `parseFlagVariant`, switch on `kind` after validating it with `isVariantKind` — TypeScript will narrow the discriminated union branch for you inside each `case`.
- In `evaluateFlags`, the audience gate rule is: a flag is eligible if `flag.audience === 'public'` OR `flag.audience === user.audience`; anything else resolves to `status: 'default'` before rules are even checked.
Useful resources
Or clone locally
git clone -b challenge/2026-04-14 https://github.com/niltonheck/typedrop.git