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.
Useful resources
Or clone locally
git clone -b challenge/2026-03-29 https://github.com/niltonheck/typedrop.git