Start with accounting invariants
A settlement algorithm is downstream of accounting. If expenses are represented with binary floating point, a mathematically zero balance can become a collection of tiny residuals. CashFlow stores values as integer cents so equality and conservation checks are exact at currency precision.
After reducing expenses, positive balances are creditors and negative balances are debtors. Their sum must be zero. Every proposed payment transfers value from a negative balance to a positive balance without changing that total.
const total = balances.reduce((sum, value) => sum + value, 0)
if (total !== 0) throw new Error("Balances must sum to zero")
// Values are integer cents: 1299 means 12.99 in the base currency.What is being minimized?
There are several different optimization goals: total money moved, number of payments, maximum payment size, or fairness between participants. When balances already sum to zero, every valid plan moves the same net obligation. CashFlow's exact solver therefore minimizes the number of payments for the given balance state.
A greedy debtor-to-creditor match is fast and often good, but it is not guaranteed to use the fewest transactions. An exact search explores valid transfers, prunes equivalent states, and remembers the best remaining transaction count.
Exact search with a defensible boundary
The search space grows combinatorially with the number of non-zero balances. The honest engineering decision is to state a limit instead of describing the solver as universally optimal. CashFlow runs exact minimum-payment search for at most 12 non-zero balances.
- Remove zero balances before selecting a strategy.
- Choose one unsettled participant and pair it with compatible opposite-sign balances.
- Apply a transfer, recurse, then restore the previous state.
- Memoize normalized balance states and prune branches that cannot beat the best plan.
- Use stable participant ordering so equal inputs produce equal outputs.
function settle(balances) {
const active = balances.filter(({ cents }) => cents !== 0)
return active.length <= 12
? exactMinimumPayments(active)
: deterministicGreedy(active)
}The deterministic fallback
Above the exact-search boundary, the fallback repeatedly matches a debtor and creditor using stable ordering. It is designed to settle every balance and return the same plan for the same input. It is not described as globally minimal.
Determinism improves testing, auditability, and user trust. Two people refreshing the same group should not see different recommendations merely because a map or database returned members in another order.
Why WebAssembly is optional
The repository contains a matching C++ implementation that can compile to WebAssembly. That is useful for runtime comparison and parity testing, but a language change does not alter the combinatorial complexity of exact search. The hosted application currently relies on the tested TypeScript solver, so the portfolio should not imply that WASM is always executing in production.
A sound result is less glamorous but more useful: the interface can report which strategy and runtime produced a plan, and both implementations must satisfy the same accounting invariants.