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