Build Splitwise
KINDAreplaces $5/mosaves $60/yrback to the verdict
A shared expense ledger where the interesting problem is the settlement graph: turning a tangle of who paid for what into the smallest set of transfers that clears it, without ever losing a penny to rounding or to a currency conversion nobody agreed on.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and the settlement computation.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why Shared money between people needs real transactions and exact integer arithmetic.
Get it Docker or your package manager. open ↗
Verify
psql connects and integer arithmetic is exact - have readyfree
Why The settlement graph is uninteresting below about six people, and rounding, multiple payers and mixed currencies all appear together only in real shared spending.
Get it Reconstruct a holiday or a shared house month with at least forty expenses, several payers and at least two currencies.
Verify
Forty expenses exist across six or more people with two currencies - decidefree
Why Every uneven split has one and the rule is enforced in code, so it belongs decided rather than emergent.
Get it Decide the allocation rule and whether it rotates, and write it down where the group can see it.
Verify
A written rule exists and is displayed in the application - decidefree
Why Debt simplification routes obligations through people who did not transact, which is correct arithmetic and sometimes socially unwelcome.
Get it Decide whether opting out is available by default, and whether original obligations are always shown.
Verify
A written note states the default and what is displayed
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- groups(id, name, default_currency, created_at, settled_at) - members(group_id, person_id, joined_at, left_at) - expenses(id, group_id, description, amount_minor, currency, rate_to_group_currency, paid_at, created_by, deleted_at) - payers(expense_id, person_id, amount_minor) · more than one person can pay for one expense - shares(expense_id, person_id, amount_minor, method) · method is equal, exact, percentage or shares - settlements(id, group_id, from_person_id, to_person_id, amount_minor, currency, confirmed_at) - edits(expense_id, ordinal, before_json, after_json, by, at) · retained, because a changed expense is a changed balance - Shares are stored as resolved amounts rather than as a rule, so the remainder allocation is a fact rather than something recomputed differently later.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/split | Shared expenses between identified people. |
DEFAULT_CURRENCY | required | EUR | The group currency. Individual expenses keep their own. |
REMAINDER_RULE | required | rotate | Who receives the odd penny. Rotating is fairer than always the first member. |
ALLOW_INTERMEDIATION_OPT_OUT | required | true | Whether a member can refuse to be routed through in settlement. |
REQUIRE_SETTLEMENT_CONFIRMATION | required | always | Present as a constant. One-sided settlement is assertion rather than payment. |
RATE_SOURCE | required | manual | Where conversion rates come from. Recorded per expense and never re-applied. |
The build, in order
Splitting money without losing a penny
Every split sums exactly to the amount paid.
Stored as amounts, not as a rule to recompute.
Files
src/split.tssrc/split.test.tsDeterministic, recorded, and rotated so it is not always the same person.
Files
src/split.tsA thousand random splits. This is the invariant everything rests on.
Files
tests/split-sum.test.ts
done when · tick each as it passeswatch out- Splitting an amount that does not divide evenly and rounding each share independently produces a total that does not match what was paid, and over a holiday's worth of expenses the discrepancy becomes visible and nobody can explain it. Allocate the remainder explicitly, rotate who receives it, and assert the sum.
Balances that survive an edit
Changing an expense changes the balances correctly and visibly.
Across the group. Any non-zero total is a bug in the split maths.
Files
src/balances.tstests/balance-zero.test.tsShared money arguments are settled by the record.
Files
src/edits.tsTo the people affected. A silently changed figure is how trust goes.
Files
src/notify.ts
done when · tick each as it passeswatch out- Editing an expense silently after people have looked at their balances is how shared money becomes an argument, because someone remembers owing a different figure and there is no record of the change. Retain the edit, and surface the balance movement to everyone it affected rather than only updating the number.
The fewest payments that clear it
A tangled group settles with as few transfers as possible.
Positive and negative balances matched, not every pair reconciled.
Files
src/simplify.tssrc/simplify.test.tsSimplification must not lose or invent a penny.
Files
src/simplify.tsRouting a stranger's debt through someone is correct and unwelcome.
Files
src/simplify.ts
done when · tick each as it passeswatch out- Simplification will happily route a debt through someone who never transacted with either party, which is mathematically correct and socially wrong, since they now owe money to a person they did not deal with. Let people opt out of intermediation, and show the original obligations alongside the simplified transfers.
More than one currency
A trip with three currencies settles fairly and explicably.
And keep it on the expense. Re-converting changes what people agreed.
Files
src/currency.tsSo a disagreement is about the rate rather than about the arithmetic.
Files
src/display.tsConversion rounding is where it usually breaks.
Files
tests/multicurrency.test.ts
done when · tick each as it passeswatch out- Converting balances at today's rate rather than the rate recorded when the expense happened means the amount someone owes changes daily without anyone spending anything, and on a two week trip that is a real and unexplainable difference. Fix the rate at entry and keep it with the expense.
Settling up, and remembering that you didproduct builder
A payment between people is recorded once and agreed by both.
One-sided settlement is assertion, not payment.
Files
src/settlements.tsPeople pay round numbers. The remainder stays owed.
Files
src/settlements.tsThe history is what a disagreement a year later is settled by.
Files
src/groups.ts
done when · tick each as it passeswatch out- A settlement recorded by the payer alone and immediately applied lets one person zero their balance by asserting they paid, which is the exact situation shared expense tracking exists to prevent. Require the recipient to confirm, and show unconfirmed settlements as pending rather than as settled.
That is the whole plan for Splitwise. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Moving actual money.
- Receipt scanning and itemisation.
- Mobile applications.
- Recurring bills between housemates.
- native iOS and Android apps, push reminders included
- your friends' existing accounts and muscle memory
- receipt scanning and itemized splits
- automatic currency conversion at live rates
- settle-up integrations with payment apps
- Reminders for outstanding balances, worded so they do not damage friendships
- Categories and a trip summary, which is what people want afterwards
- Splitting by shares that change per expense, for households with uneven arrangements
Need the files? The project pack on the verdict page hands your agent the whole brief · more personal finance.