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.
Useful resources
Or clone locally
git clone -b challenge/2026-08-10 https://github.com/niltonheck/typedrop.git