TypeDrop

2026-08-10 Challenge

2026-08-10 Easy

Typed GroupBy Aggregator with Summary Statistics

You're building the reporting layer for a sales dashboard that receives a flat list of transaction records and must group them by a chosen key, then compute per-group summary statistics (count, sum, min, max, average) — all without losing the original record's type.

Goals

  • Implement `groupBy` so records are bucketed by the chosen key field and the return type is exactly `GroupByResult<T, K>`.
  • Compute a correct `NumericSummary` (count, sum, min, max, average) for every numeric field on T inside each group's `stats` object.
  • Implement the `NumericKeys<T>` utility type using a mapped + conditional type so it resolves to only the number-valued keys of T.
  • Ensure the implementation compiles under `strict: true` with no `any`, type assertions, or manual field listings.
challenge.ts
// Key types at a glance

interface Transaction {
  id: string;
  region: "north" | "south" | "east" | "west";
  category: "hardware" | "software" | "services";
  salesperson: string;
  amount: number;   // ← numeric field
  units: number;    // ← numeric field
}

interface NumericSummary {
  count: number; sum: number;
  min: number;   max: number; average: number;
}

// stats only contains keys whose value type is `number`
type Group<T, K extends keyof T> = {
  key: T[K];
  items: T[];
  stats: { [F in keyof T as T[F] extends number ? F : never]: NumericSummary };
};

export function groupBy<T extends object, K extends keyof T>(
  records: T[],
  key: K
): Record<string, Group<T, K>> { /* TODO */ throw new Error("Not implemented"); }
Hints (click to reveal)

Hints

  • To discover numeric field names at runtime, iterate `Object.keys(records[0])` and filter by `typeof record[field] === 'number'` — mirror what your conditional type does at the type level.
  • The `stats` object in `Group<T, K>` uses a mapped type with `as` key remapping — your runtime code must produce an object with the same shape; build it field-by-field in a loop.
  • Cast the accumulated `stats` object to the correct type only at the boundary where TypeScript can't infer the shape — but avoid `as any`; prefer a well-typed intermediate `Record<string, NumericSummary>` and a single targeted cast.

Or clone locally

git clone -b challenge/2026-08-10 https://github.com/niltonheck/typedrop.git