TypeDrop

2026-09-02 Challenge

2026-09-02 Easy

Typed Shopping Cart with Discount Strategy & Line-Item Aggregation

You're building the checkout engine for a small e-commerce storefront. Each cart holds typed line items, and the store supports several mutually-exclusive discount strategies (percentage off, fixed amount off, buy-X-get-Y free). The compiler must guarantee that every discount kind is fully handled and that the cart summary is always correctly typed.

Goals

  • Derive `DiscountKind` from the `Discount` union using an indexed access type, and build `DiscountByKind` as a mapped type using `Extract`.
  • Implement `computeSubtotal` to aggregate line-item costs in a single pass.
  • Implement `computeDiscountAmount` with an exhaustive `switch` on `discount.kind`, correctly handling all three strategies including the buy-X-get-Y free calculation.
  • Implement `buildCartSummary` and `getDiscountLabel` so the compiler guarantees every discount branch is covered and all return types are fully typed.
challenge.ts
export interface LineItem {
  readonly product: Product;
  readonly quantity: number;
}

export type Discount =
  | { kind: "percentage"; percent: number }
  | { kind: "fixed"; amountCents: number }
  | { kind: "buyXgetY"; productId: string; buyQuantity: number; freeQuantity: number };

export type DiscountKind = Discount["kind"];

export type DiscountByKind = {
  [K in DiscountKind]: Extract<Discount, { kind: K }>;
};

export function computeSubtotal(lineItems: readonly LineItem[]): number;

export function computeDiscountAmount(
  subtotalCents: number,
  discount: Discount,
  lineItems: readonly LineItem[]
): number;

export function buildCartSummary(
  lineItems: readonly LineItem[],
  discount: Discount | null
): CartSummary;
Hints (click to reveal)

Hints

  • For `DiscountByKind`, iterate over `DiscountKind` with a mapped type and use `Extract<Discount, { kind: K }>` as the value — TypeScript will narrow each member automatically.
  • In `computeDiscountAmount`, a `switch (discount.kind)` with a `default: { const _exhaustive: never = discount; }` guard ensures the compiler catches any unhandled future discount kinds.
  • For buy-X-get-Y, the number of free units from `n` total units is `Math.floor(n / (buyQuantity + freeQuantity)) * freeQuantity`.

Or clone locally

git clone -b challenge/2026-09-02 https://github.com/niltonheck/typedrop.git