TypeDrop

A new TypeScript challenge every day. Sharpen your types.

TypeDrop delivers a fresh TypeScript challenge every day, generated by AI. Pick a challenge, open it in StackBlitz (preferred) or CodeSandbox (or clone it locally), and make the tests pass. No accounts, no setup — just you and the type system.

Learn more on GitHub →

2026-04-07 Hard

Typed Query Plan Optimizer

You're building the query execution layer for an in-browser analytics engine. Raw query descriptors arrive as unknown JSON; your optimizer must validate them, build a typed expression tree, walk it with a recursive visitor, and return a fully typed execution plan with cost estimates — with zero `any`.

Goals

  • Define the `Expr` and `QueryNode` discriminated union types and the generic `ExprVisitor<R>` interface.
  • Implement `walkExpr` with an exhaustive switch so TypeScript catches any missing branch at compile time.
  • Implement `parseExpr` and `parseQueryNode` to safely validate unknown blobs into fully typed trees, returning `Result<T, ParseError>`.
  • Implement `estimateRows`, `buildPlan`, and the top-level `optimize` function, propagating errors and accumulating large-table warnings correctly.
challenge.ts

// Core types and main entry point

export type Expr =
  | { kind: "literal"; value: string | number | boolean | null }
  | { kind: "column";  table: string; name: string }
  | { kind: "binary";  op: BinaryOp; left: Expr; right: Expr }
  | { kind: "agg";     fn: AggFn;    source: ColumnRef }
  | { kind: "case";    whens: ReadonlyArray<{ when: Expr; then: Expr }>; else: Expr };

export type QueryNode =
  | { op: "scan";    table: string; alias: string }
  | { op: "filter";  predicate: Expr; child: QueryNode }
  | { op: "project"; outputs: ReadonlyArray<{ alias: string; expr: Expr }>; child: QueryNode }
  | { op: "join";    condition: Expr; left: QueryNode; right: QueryNode }
  | { op: "agg";     groupBy: ReadonlyArray<ColumnRef>; aggregates: ReadonlyArray<{ alias: string; expr: AggExpr }>; child: QueryNode };

export interface ExprVisitor<R> {
  visitLiteral(e: LiteralExpr): R;
  visitColumn (e: ColumnRef):   R;
  visitBinary (e: BinaryExpr):  R;
  visitAgg    (e: AggExpr):     R;
  visitCase   (e: CaseExpr):    R;
}

// Main entry point — parse, plan, and cost an unknown query descriptor
export function optimize(raw: unknown): Result<ExecutionPlan, OptimizerError>;
Hints (click to reveal)

Hints

  • Use a `switch (expr.kind)` with a `default: satisfies never` (or an exhaustive helper) to guarantee every Expr variant is handled in `walkExpr`.
  • In `parseExpr` and `parseQueryNode`, narrow `unknown` step-by-step: check `typeof`, then `'kind' in raw`, then check specific string values before casting to the target type.
  • The `totalCost` in `optimize` is the sum of `estimatedRows` across every node in the `PlanNode` tree — write a small private recursive helper that mirrors the structure of `PlanNode`.

Or clone locally

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