Design Splitwise / Expense Sharing (Low-Level Design)
Design an expense-sharing app like Splitwise in an LLD interview: split strategies (equal/exact/percentage), the who-owes-whom balance model, and the debt-simplification bonus. Classes, UML, and design decisions.
🟠Low-Level Design · Case Study · Senior
"Design Splitwise" (an expense-sharing app) is a favourite because it has a clean object model and two interesting sub-problems: supporting different ways to split an expense, and tracking who owes whom so debts can be simplified. It's a great showcase for the Strategy pattern plus a little graph thinking.
Step 1 — Requirements
- Functional: users and groups; add an expense (who paid, total, participants) split equally, by exact amounts, or by percentage; show each person's balance (owes / is owed); settle up; optionally simplify debts.
- Non-functional: balances always add up to zero, splitting is extensible, correct to the cent.
Step 2 — The classes
- User, Group (a set of users).
- Expense — amount, who paid, and a list of Splits (participant → share).
- SplitStrategy (interface) → EqualSplit, ExactSplit, PercentSplit — computes each participant's share.
- BalanceSheet / ExpenseManager — tracks pairwise balances (
who → whom → amount) and updates them on each expense.
Step 3 — Splitting = Strategy
🟠The different split types are interchangeable algorithms — textbook Strategy:
interface SplitStrategy { Map<User,Double> split(double amount, List<User> users, double[] values); }
class EqualSplit implements SplitStrategy {
public Map<User,Double> split(double amt, List<User> users, double[] v){
double share = amt / users.size(); // everyone equal
// return each user → share
}
}
class PercentSplit implements SplitStrategy { /* share = amt * pct[i]/100 */ }
class ExactSplit implements SplitStrategy { /* share = given exact values */ }
Adding a new split type (e.g. "by shares") is a new class — no changes elsewhere. Each strategy also validates (exact splits must sum to the total; percentages to 100).
Step 4 — Tracking balances
🔴 The core data structure: a balance map — balances[A][B] = amount A owes B (negative means B owes A). When Alice pays $30 split equally among Alice, Bob, Carol, each owes $10; Bob and Carol each owe Alice $10, so you update balances[Bob][Alice] += 10 and balances[Carol][Alice] += 10.
Step 5 — The senior bonus: simplify debts
🔴 A classic follow-up: minimise the number of transactions to settle up. If Bob owes Alice $10 and Carol owes Bob $10, don't do two payments — have Carol pay Alice directly. This is a debt-simplification problem: compute each person's net balance (total owed − total owing), then match the biggest debtor to the biggest creditor repeatedly (a greedy min-cash-flow). Mentioning "reduce to net balances, then greedily settle max debtor↔max creditor" is a standout senior answer.
Step 6 — Design notes
🟠Keep money in integer cents (or BigDecimal) to avoid floating-point errors — a real Splitwise concern. Validate splits sum to the total. Groups are just a convenience over the same balance engine. Optionally record an Expense history for an audit trail.
The interview-ready walkthrough
"An Expense has a payer and splits computed by a pluggable SplitStrategy — Equal, Exact, Percentage — each validating its input; new split types are new classes. I track a balance map of who-owes-whom, updated per expense, and provide settle-up. As a bonus I'd simplify debts by reducing to net balances and greedily matching the biggest debtor to the biggest creditor to minimise transactions. I'd use integer cents / BigDecimal to avoid rounding errors." That hits Strategy, the balance model, and the debt-simplification bonus."
What to read next
- Strategy Pattern — used for the split types
- Design an LRU Cache — the next LLD (data-structure focused)
- Design an ATM — another money-handling design
- ← The complete System Design guide (hub)