TypeDrop
2026-10-25 Challenge
2026-10-25
Easy
Typed Recipe Cost Calculator
You're building the pricing engine for a meal-kit delivery app. Recipes are made up of ingredients with quantities and units, and you need to calculate costs, flag expensive recipes, and summarize a weekly menu — with the compiler enforcing every unit, field name, and summary shape.
Goals
- Implement `toBaseUnit` using a `Record<Unit, number>` multiplier map to convert any quantity to its ingredient's base unit.
- Implement `calculateRecipeCost` to look up prices, convert units, sum ingredient costs, and populate a fully-typed `RecipeCost` object.
- Implement `summarizeMenu` to aggregate an array of `RecipeCost` values into a `MenuSummary` — including `Pick`-typed cheapest/most-expensive fields — and throw a `RangeError` on empty input.
- Implement `filterByMaxCostPerServing` to return a sorted, filtered `readonly RecipeCost[]` within a given per-serving budget.
challenge.ts
/** All supported units of measure */
type Unit = "g" | "kg" | "ml" | "l" | "unit";
interface IngredientPrice {
readonly name: string;
readonly pricePerBaseUnit: number;
readonly baseUnit: Extract<Unit, "g" | "ml" | "unit">;
}
interface RecipeCost {
readonly recipeId: string;
readonly recipeName: string;
readonly totalCost: number;
readonly costPerServing: number;
readonly isExpensive: boolean;
}
interface MenuSummary {
readonly totalRecipes: number;
readonly averageCostPerServing: number;
readonly cheapestRecipe: Pick<RecipeCost, "recipeId" | "recipeName" | "costPerServing">;
readonly mostExpensiveRecipe: Pick<RecipeCost, "recipeId" | "recipeName" | "costPerServing">;
readonly expensiveCount: number;
}
// Convert any Unit quantity to its base unit (g, ml, or unit)
function toBaseUnit(quantity: number, unit: Unit): number { ... }
// Calculate full cost breakdown for one recipe
function calculateRecipeCost(
recipe: Recipe,
prices: readonly IngredientPrice[],
expensiveThreshold: number
): RecipeCost { ... }
// Aggregate an array of RecipeCost into a MenuSummary
function summarizeMenu(costs: readonly RecipeCost[]): MenuSummary { ... }
Hints (click to reveal)
Hints
- A `Record<Unit, number>` multiplier (e.g. `{ kg: 1000, l: 1000, g: 1, ml: 1, unit: 1 }`) makes `toBaseUnit` a one-liner — no `if`/`switch` needed.
- Remember that `Array.find` returns `T | undefined`; use optional chaining (`?.`) or a nullish fallback to avoid a type error when an ingredient isn't in the price list.
- `Pick<RecipeCost, 'recipeId' | 'recipeName' | 'costPerServing'>` is already the declared type in `MenuSummary` — you only need to return an object with exactly those three keys, which TypeScript will verify structurally.
Useful resources
Or clone locally
git clone -b challenge/2026-10-25 https://github.com/niltonheck/typedrop.git