TypeDrop
2026-06-10 Challenge
2026-06-10
Easy
Typed Shopping Cart Discount Engine
You're building the checkout engine for an e-commerce platform. Cart items arrive as `unknown` from a storefront API; your engine must validate them, apply the correct typed discount rule to each item, and return a strongly-typed order summary — with zero `any`.
Goals
- Implement `isValidDiscountRule` and `isValidCartItem` as type predicates that safely narrow `unknown` input, rejecting any structurally invalid or out-of-range data.
- Implement `applyDiscount` with an exhaustive branch for each of the four `DiscountRule` variants, computing the correct `lineTotal` and `discountSaved`.
- Implement `buildOrderSummary` to validate raw items, skip invalid ones, apply discounts, and aggregate all lines into a correctly rounded `OrderSummary`.
- Use only TypeScript's type system features (discriminated unions, type predicates, Pick) — no `any`, no type assertions.
challenge.ts
// Core discount union — four variants keyed by `kind`
type DiscountRule =
| { kind: "percentage"; percent: number }
| { kind: "flat"; amount: number }
| { kind: "buyNgetM"; buyN: number; getM: number }
| { kind: "none" };
type CartItem = {
id: string;
name: string;
unitPrice: number;
quantity: number;
discount: DiscountRule;
};
type OrderLineItem = {
id: string; name: string; unitPrice: number; quantity: number;
discountKind: DiscountRule["kind"];
lineTotal: number;
discountSaved: number;
};
// Validate unknown → CartItem
function isValidCartItem(value: unknown): value is CartItem { ... }
// Apply the correct discount branch, return Pick<OrderLineItem, ...>
function applyDiscount(item: CartItem): Pick<OrderLineItem, "lineTotal" | "discountSaved"> { ... }
// Entry point: validate, apply, aggregate
function buildOrderSummary(rawItems: unknown[]): OrderSummary { ... }
Hints (click to reveal)
Hints
- Start `isValidDiscountRule` by checking `typeof value === 'object' && value !== null` and then narrowing on the `kind` field with a type-safe property access.
- In `applyDiscount`, a `switch` on `item.discount.kind` gives you exhaustive narrowing — TypeScript will know which variant's fields are available in each branch.
- For the `buyNgetM` calculation: `Math.floor(quantity / buyN)` gives you the number of full groups, each yielding `getM` free items.
Useful resources
Or clone locally
git clone -b challenge/2026-06-10 https://github.com/niltonheck/typedrop.git