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.

Or clone locally

git clone -b challenge/2026-07-05 https://github.com/niltonheck/typedrop.git