TypeDrop

2026-09-26 Challenge

2026-09-26 Easy

Typed Expense Splitter

You're building the core logic for a group expense-splitting app (think Splitwise). Friends log shared expenses, and the app must calculate how much each person owes or is owed — producing a strongly-typed settlement summary the compiler fully understands.

Goals

  • Implement `computeBalances` using discriminated-union narrowing on `SplitRule.kind` to compute each participant's net balance in cents.
  • Implement `minimiseSettlements` with a greedy creditor/debtor matching algorithm that produces the minimum number of transfers.
  • Implement `splitExpenses` to compose the two functions above and return an explicitly-typed `SplitSummary`.
  • Implement `formatSettlement` with a template literal return type that formats cent amounts into a `$dollars.cents` string.
challenge.ts

// Key domain types
type Participant = string;

type SplitRule =
  | { kind: "equal" }
  | { kind: "shares"; participants: Record<Participant, number> }
  | { kind: "exact";  participants: Record<Participant, number> };

interface Expense {
  id: string;
  description: string;
  paidBy: Participant;
  amountCents: number;   // integer cents, > 0
  splitRule: SplitRule;
}

type Balances = Record<Participant, number>; // + owed, - owes

interface Settlement { from: Participant; to: Participant; amountCents: number; }
interface SplitSummary { balances: Balances; settlements: Settlement[]; settled: boolean; }

// Functions you must implement:
function computeBalances(expenses: Expense[], participants: Participant[]): Balances { … }
function minimiseSettlements(balances: Balances): Settlement[] { … }
function splitExpenses(expenses: Expense[], participants: Participant[]): SplitSummary { … }
function formatSettlement(s: Settlement): `${string} owes ${string} $${string}` { … }
Hints (click to reveal)

Hints

  • When narrowing `SplitRule`, a `switch (rule.kind)` block gives TypeScript full discriminated-union narrowing — no type assertions needed.
  • For the template literal return type, derive dollars with `Math.floor(amountCents / 100)` and cents with `(amountCents % 100).toString().padStart(2, '0')`.
  • In `minimiseSettlements`, sort creditors and debtors by absolute balance descending before the greedy loop so the largest transfers are matched first.

Or clone locally

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