TypeDrop

2026-06-12 Challenge

2026-06-12 Easy

Typed Recipe Ingredient Scaler

You're building the recipe management feature for a meal-planning app. Raw recipe data arrives as `unknown` from a user-uploaded JSON file; your engine must validate it, scale each ingredient's quantity to a requested serving size, and return a strongly-typed scaled recipe — with zero `any`.

Goals

  • Derive the `Unit` union type directly from the `VALID_UNITS` const array using `typeof` and indexed access — no manual duplication.
  • Implement `parseIngredient` and `parseRecipe` to safely narrow `unknown` inputs, returning `null` for any invalid field rather than throwing.
  • Implement `scaleRecipe` to proportionally adjust every ingredient quantity (rounded to 2 decimal places) and attach `targetServings` to the result.
  • Define the `IngredientLine` template literal type and use it as the explicit return type of `formatIngredient`.
challenge.ts
// Key types & main function signatures

export const VALID_UNITS = [
  "g", "kg", "ml", "l", "tsp", "tbsp", "cup", "piece",
] as const;

export type Unit        = typeof VALID_UNITS[number];
export type Ingredient  = { name: string; quantity: number; unit: Unit };
export type Recipe      = { title: string; defaultServings: number; ingredients: Ingredient[] };
export type ScaledRecipe = Recipe & { targetServings: number };
export type IngredientLine = `${string}: ${number} ${Unit}`;

export function parseIngredient(value: unknown): Ingredient | null { /* TODO */ }
export function parseRecipe(value: unknown): Recipe | null { /* TODO */ }
export function scaleRecipe(recipe: Recipe, targetServings: number): ScaledRecipe | null { /* TODO */ }
export function formatIngredient(ingredient: Ingredient): IngredientLine { /* TODO */ }
Hints (click to reveal)

Hints

  • You can derive `Unit` with a single line: `typeof VALID_UNITS[number]` — no need to list the strings twice.
  • To check a unit at runtime, `(VALID_UNITS as readonly string[]).includes(unit)` works, but you'll still need a type predicate or assertion-free cast to satisfy the compiler.
  • Template literal types like `` `${string}: ${number} ${Unit}` `` are valid return-type annotations — TypeScript will accept a string literal that matches the pattern.

Or clone locally

git clone -b challenge/2026-06-12 https://github.com/niltonheck/typedrop.git