TypeDrop
2026-09-19 Challenge
2026-09-19
Easy
Typed Expense Report Summarizer
You're building the core logic for a corporate expense management tool. Finance teams upload raw expense entries and need a strongly-typed summary grouped by category, with per-category totals and an overall grand total — all shapes enforced by the compiler.
Goals
- Define `PartialCategoryMap<V>` as a mapped type over `ExpenseCategory` with optional values, and `NumericExpenseFields` using key remapping with a conditional type.
- Implement `extractCategories` to return unique `ExpenseCategory` values in first-seen order without widening the type.
- Implement `groupByCategory` and `summarizeCategory` to correctly group expenses and compute per-category totals, counts, and the largest expense.
- Implement `buildExpenseReport` to compose the above helpers into a full `ExpenseReport`, including grand total, entry count, and ISO date range.
challenge.ts
export type ExpenseCategory =
| "travel" | "meals" | "software"
| "hardware" | "office" | "other";
export interface Expense {
id: string;
submittedBy: string;
category: ExpenseCategory;
amountUSD: number;
description: string;
date: string; // ISO 8601
}
export interface CategorySummary {
category: ExpenseCategory;
totalUSD: number;
count: number;
largestExpense: Expense;
}
export interface ExpenseReport {
byCategory: Record<ExpenseCategory, CategorySummary>;
grandTotalUSD: number;
totalCount: number;
earliestDate: string | null;
latestDate: string | null;
}
// Your main entry point:
export function buildExpenseReport(expenses: Expense[]): ExpenseReport {
throw new Error("Not implemented");
}
Hints (click to reveal)
Hints
- For `NumericExpenseFields`, use a mapped type with `as` key remapping: `[K in keyof Expense as Expense[K] extends number ? K : never]`.
- For `extractCategories`, a `Set<ExpenseCategory>` guarantees uniqueness while preserving insertion order — spread it into an array to return.
- For `buildExpenseReport`, build a `PartialCategoryMap<CategorySummary>` first, then cast it to `Record<ExpenseCategory, CategorySummary>` only at the final assignment — this is the one safe structural cast the design allows.
Useful resources
Or clone locally
git clone -b challenge/2026-09-19 https://github.com/niltonheck/typedrop.git