Product engineering
Why a bill-splitting app should refuse to convert currencies
· 6 min read · DetSol
Expenddy handles eight currencies and converts between none of them. That reads like an unfinished feature until you ask the only question that matters: when the rate moves, which of your friends absorbs the difference?
The request is completely reasonable
It arrives in a predictable form. Someone takes a trip: flights booked at home in one currency, hotel paid in another, dinners in a third. They come back, open the group, and see three separate balances instead of one number. The obvious ask is: just add them up.
The reason we do not is not technical. Fetching a rate is a few lines of code and a paid API. The problem is that the moment the software adds those numbers together, it has made a decision on someone's behalf that costs them real money — and it has no basis on which to make it.
Four questions with no defensible default
- Which date's rate? The day the expense happened, the day it was entered, or the day the debt is settled? Between a trip and a repayment there can be weeks, and on a volatile pair the difference is not rounding.
- Whose rate? A central bank reference rate, an interbank mid-market rate, or the rate the person actually got — which included their card issuer's margin and possibly a cash-withdrawal fee?
- Who absorbs the movement? If the rate moves between the expense and the settlement, one party gains and the other loses. Converting silently picks a winner.
- What happens when it is disputed? The person paying has a number from their own bank statement. The app has a different number. There is no arbitrator, because the app is not a party to the transaction and holds no money.
A payment provider can answer all four, because it executes the exchange and can tell you the rate it used and charge you for it. A shared expense record executes nothing. It would be inventing a rate and presenting it with the same confidence as an amount someone actually typed in.
The asymmetry that settles it
A balance shown in a currency someone actually spent is a fact they can check against their own statement. A balance produced by conversion is an estimate wearing the clothes of a fact.
That distinction is the whole argument. Expenddy's job is to be the record two people agree on when they have forgotten the details. Every feature is worth having only insofar as it makes that record more trustworthy. A converted total is less checkable than the numbers it replaced, and it is less checkable in a way that is invisible — it looks tidier, which is exactly the problem.
What Expenddy does instead
Each expense is recorded in the currency it was actually paid in, and balances net separately for each currency. The settlement calculation is scoped by currency at the database level rather than filtered in the interface, so a payment in one currency cannot clear a debt in another even by accident. A group that spent in three currencies shows three balances, and each one is a number both people can verify independently.
Settling across them is then a human decision, made once, by the two people it affects — who can agree a rate, or simply settle each currency separately, or decide the smallest balance is not worth moving money for. All three are reasonable. None of them is a decision the software is qualified to make for them.
The general principle, which is not about currencies
Software is constantly asked to smooth over ambiguity, and smoothing is usually the right call — defaults, autocomplete, sensible fallbacks all remove decisions nobody wanted to make. The exception is when the ambiguity is the point of disagreement between two people, and the software is not a party to it.
The test we apply, and it generalises well beyond expense apps: could we defend this computed number to the person it costs money, using only information they can check themselves? If not, show them the inputs and let them decide. Refusing to compute is sometimes the more careful engineering decision, and it is nearly always the one that ages better.