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.

Or clone locally

git clone -b challenge/2026-06-10 https://github.com/niltonheck/typedrop.git