TypeDrop
2026-07-05 Challenge
2026-07-05
Medium
Typed Feature Flag Evaluator with Audience Targeting
You're building the feature-flag evaluation engine for a SaaS platform. Raw flag configurations arrive as `unknown` from a remote config service; your engine must validate them into strongly-typed `FeatureFlag` records, evaluate each flag against a typed `UserContext` using discriminated targeting rules, and return a fully-typed `EvaluationResult` per flag — with zero `any`.
Goals
- Define a three-variant `TargetingRule` discriminated union and a three-variant `EvaluationReason` discriminated union with no `any`.
- Implement `parseFeatureFlag` that validates an `unknown` payload into a fully-typed `FeatureFlag`, throwing descriptive errors for missing or mis-typed fields.
- Implement `evaluateFlag` that applies the master kill-switch, then iterates targeting rules in order (percentage / allowlist / attribute), returning a typed `EvaluationResult` with the correct reason variant.
- Implement `evaluateAll` that processes a mixed array of raw flags, collecting parse errors by index while still evaluating all successfully parsed flags.
challenge.ts
// Core types and main function signatures
type PercentageRule = { kind: "percentage"; rolloutPercent: number };
type AllowlistRule = { kind: "allowlist"; userIds: string[] };
type AttributeRule = { kind: "attribute"; key: string; values: string[] };
type TargetingRule = PercentageRule | AllowlistRule | AttributeRule;
type FeatureFlag = {
id: string; name: string; enabled: boolean;
rules: TargetingRule[]; defaultValue: boolean;
};
type UserContext = { userId: string; attributes: Record<string, string> };
type EvaluationReason =
| { kind: "disabled" }
| { kind: "rule_match"; ruleIndex: number; ruleKind: TargetingRule["kind"] }
| { kind: "default" };
type EvaluationResult = { flagId: string; value: boolean; reason: EvaluationReason };
declare function parseFeatureFlag(raw: unknown): FeatureFlag;
declare function evaluateFlag(flag: FeatureFlag, ctx: UserContext): EvaluationResult;
declare function evaluateAll(
rawFlags: unknown[], ctx: UserContext
): { results: EvaluationResult[]; parseErrors: Array<{ index: number; message: string }> };
Hints (click to reveal)
Hints
- Use `TargetingRule["kind"]` as an indexed access type for the `ruleKind` field in `EvaluationReason` — this keeps the union in sync automatically.
- In `parseFeatureFlag`, narrow each field with `typeof` guards and `Array.isArray` before assigning; never widen to `any` just to read a property — use `typeof raw === 'object' && raw !== null` first, then cast via a local `const obj = raw as Record<string, unknown>` only after confirming it's an object (a `Record<string, unknown>` assertion is the one safe bridge from `unknown` to keyed access).
- For the percentage rule, `hashUserId(ctx.userId) % 100 < rolloutPercent` is the entire match condition — remember the helper is already exported for you.
Useful resources
Or clone locally
git clone -b challenge/2026-07-05 https://github.com/niltonheck/typedrop.git