TypeDrop

2026-09-23 Challenge

2026-09-23 Easy

Typed Recipe Ingredient Scaler

You're building the core logic for a recipe management app. Home cooks can scale any recipe up or down by a multiplier, and the app must return a fully-typed scaled recipe where every ingredient's quantity has been adjusted and units are preserved exactly as the compiler understands them.

Goals

  • Define the `Unit` union type and `Ingredient` / `Recipe` interfaces.
  • Derive `ScaledIngredient` and `ScaledRecipe` exclusively with utility types (Omit, Pick, intersection) — no hand-written interfaces.
  • Implement `scaleRecipe` so quantities are rounded to 2 decimal places and servings to the nearest integer.
  • Implement the generic `findIngredientsByUnit<U extends Unit>` returning `readonly (Ingredient & { unit: U })[]`.
challenge.ts
// Key types you must define and the two function signatures:

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

interface Ingredient { name: string; quantity: number; unit: Unit }
interface Recipe     { title: string; servings: number; ingredients: readonly Ingredient[] }

// Derived with utility types (no hand-written interfaces!):
type ScaledIngredient = /* Omit/Pick/& combo */ never;
type ScaledRecipe     = /* Omit/& combo      */ never;

function scaleRecipe(recipe: Recipe, multiplier: number): ScaledRecipe { ... }

function findIngredientsByUnit<U extends Unit>(
  recipe: Recipe,
  unit: U
): readonly (Ingredient & { unit: U })[] { ... }
Hints (click to reveal)

Hints

  • For `ScaledIngredient`, try `Omit<Ingredient, 'quantity'> & { quantity: number }` — then ask yourself whether that's meaningfully different from just `Ingredient`, and whether a `Pick`-based approach communicates intent more clearly.
  • For `ScaledRecipe`, `Omit<Recipe, 'servings' | 'ingredients'> & { servings: number; ingredients: readonly ScaledIngredient[] }` is the right shape — no new keywords needed.
  • Inside `findIngredientsByUnit`, a simple `.filter()` with a type predicate `(i): i is Ingredient & { unit: U } => i.unit === unit` will satisfy the compiler without any casting.

Or clone locally

git clone -b challenge/2026-09-23 https://github.com/niltonheck/typedrop.git