TypeDrop

2026-03-29 Challenge

2026-03-29 Easy

Typed Recipe Ingredient Scaler

You're building the recipe customization feature for a cooking app. Users can scale any recipe up or down by a multiplier, and your engine must validate raw ingredient inputs, convert between units, and return a fully typed scaled recipe — with zero `any`.

Goals

  • Define `UnitGroup` as a discriminated union that cleanly separates volume and weight units at the type level.
  • Implement `parseIngredient` to narrow all `unknown` fields and return a typed `Result<Ingredient, ValidationError>` with ordered validation.
  • Implement `scaleIngredient` to produce a `ScaledIngredient` that records the branded `Multiplier` used.
  • Implement `scaleRecipe` to orchestrate validation and scaling across an array of raw inputs, collecting both successes and indexed failures into a `ScaledRecipeResult`.
challenge.ts
// Key types & main function signatures

export type Multiplier     = number & { readonly __brand: "Multiplier" };
export type IngredientName = string & { readonly __brand: "IngredientName" };

export type UnitGroup =
  | { kind: "volume"; unit: VolumeUnit }
  | { kind: "weight"; unit: WeightUnit };

export type Result<T, E> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

export interface RawIngredient { name: unknown; quantity: unknown; unit: unknown; }
export interface Ingredient    { name: IngredientName; quantity: number; unitGroup: UnitGroup; }
export interface ScaledIngredient extends Ingredient { readonly scaledBy: Multiplier; }

// --- Functions you must implement ---
export function parseIngredient(raw: RawIngredient): Result<Ingredient, ValidationError>;
export function scaleIngredient(ingredient: Ingredient, multiplier: Multiplier): ScaledIngredient;
export function scaleRecipe(rawIngredients: RawIngredient[], rawMultiplier: number): ScaledRecipeResult;
Hints (click to reveal)

Hints

  • To narrow `unknown` to a specific type, use `typeof value === 'string'` or `typeof value === 'number'` guards — TypeScript will narrow the type inside the `if` block.
  • A `Set<VolumeUnit>` with a `.has()` check is a clean way to narrow an `unknown` unit string into a `VolumeUnit` — but you'll need to first confirm it's a `string`.
  • Branded types like `Multiplier` can only be created via a trusted constructor (like `makeMultiplier`); call it inside `scaleRecipe` to avoid unsafe casts.

Or clone locally

git clone -b challenge/2026-03-29 https://github.com/niltonheck/typedrop.git