TypeDrop

2026-04-08 Challenge

2026-04-08 Easy

Typed Shopping Cart Summarizer

You're building the checkout screen for an e-commerce app. Raw cart items arrive from local storage as unknown blobs; your engine must validate them, apply typed discount rules, and return a fully typed order summary — with zero `any`.

Goals

  • Implement `validateCartItem` to safely narrow an `unknown` blob into a fully typed `CartItem`, returning a `Result<CartItem, string>` on any validation failure.
  • Implement `calculateDiscount` to exhaustively handle all three `Discount` variants — including the `buyNGetOneFree` per-category logic — and clamp the result to the subtotal.
  • Implement `summarizeCart` to orchestrate validation, line building, and discount application, returning a `Result<OrderSummary, CartValidationError>` with the correct error index on the first invalid item.
  • Ensure all discriminated-union branches are exhaustively matched and no `any` or `as` type assertions are used anywhere.
challenge.ts
// Key types & main function signature

type Category = "electronics" | "clothing" | "food" | "books";

interface CartItem {
  id: string; name: string; category: Category;
  priceUsd: number; quantity: number;
}

type Discount =
  | { kind: "flat";           amountUsd: number }
  | { kind: "percent";        percent: number   }
  | { kind: "buyNGetOneFree"; category: Category; n: number };

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

interface OrderSummary {
  lines: SummaryLine[];
  subtotalUsd: number;
  discountAmountUsd: number;
  totalUsd: number;
  itemCount: number;
}

// Validate one unknown blob → CartItem
declare function validateCartItem(raw: unknown): Result<CartItem, string>;

// Compute total discount across all discount rules
declare function calculateDiscount(
  items: CartItem[], subtotal: number, discounts: Discount[]
): number;

// Full pipeline: validate → build lines → apply discounts → summarize
declare function summarizeCart(
  rawItems: unknown[], discounts: Discount[]
): Result<OrderSummary, CartValidationError>;
Hints (click to reveal)

Hints

  • Use a `switch (discount.kind)` with all three branches — TypeScript will narrow the type inside each case automatically.
  • To check that a value belongs to the `Category` union at runtime, call `VALID_CATEGORIES.has(value as Category)` — but first confirm `typeof value === 'string'` so strict mode is satisfied without a raw cast.
  • For `buyNGetOneFree`, filter `items` by `category`, then use `Math.floor(totalQty / (n + 1))` and `Math.min(...prices)` to compute the free-item value.

Or clone locally

git clone -b challenge/2026-04-08 https://github.com/niltonheck/typedrop.git