TypeDrop

2026-10-01 Challenge

2026-10-01 Easy

Typed Recipe Ingredient Scaler

You're building the core logic for a recipe app that lets users scale ingredient quantities up or down. Given a recipe with strongly-typed ingredients and units, the app must produce a scaled version with correctly adjusted amounts and a human-readable shopping list — all verified by the compiler.

Goals

  • Define `ScaledIngredient` as a utility/mapped type derived from `Ingredient` (no hand-written duplicates).
  • Implement `scaleRecipe` to multiply ingredient amounts by the correct factor, round to 2 decimal places, and throw a `RangeError` for invalid servings.
  • Implement `toShoppingList` using type narrowing (not casts) to produce the correct discriminated-union `ShoppingListEntry` variant for each ingredient.
  • Implement `mergeShoppingLists` using a `Map` to combine entries with the same name AND kind in O(n) time.
challenge.ts

// Key types you'll work with:

type Unit = "tsp" | "tbsp" | "cup" | "oz" | "lb"
           | "g" | "kg" | "ml" | "l" | "whole";

interface Ingredient {
  readonly name: string;
  readonly amount: number;
  readonly unit: Unit;
}

// TODO: derive this from Ingredient using a utility type
type ScaledIngredient = never;

interface Recipe {
  readonly title: string;
  readonly servings: number;
  readonly ingredients: readonly Ingredient[];
}

// Main function signatures:
function scaleRecipe(recipe: Recipe, targetServings: number): ScaleResult { ... }
function toShoppingList(ingredients: readonly ScaledIngredient[]): ShoppingListEntry[] { ... }
function mergeShoppingLists(listA: ShoppingListEntry[], listB: ShoppingListEntry[]): ShoppingListEntry[] { ... }
Hints (click to reveal)

Hints

  • For `ScaledIngredient`, look at how `Readonly<T>` or a simple mapped type can re-use `Ingredient`'s fields without rewriting them.
  • In `toShoppingList`, a plain `if (ingredient.unit === 'whole')` check is enough for TypeScript to narrow the type — no `as` needed.
  • For `mergeShoppingLists`, use a composite key like `${entry.name}::${entry.kind}` as your `Map` key to distinguish same-name different-kind entries.

Or clone locally

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