Expense Sharing App
Design the core of a Splitwise-style app: users form groups, add expenses paid by one person and split equally, by exact amounts, or by percentage, and see who owes whom. Support settling up and simplifying debts within a group.
Clarifying Questions
- Can an expense have several payers, or exactly one?
- Which split types are needed: equal, exact amounts, percentages, or shares?
- Is everything in one currency, or do groups mix currencies?
- How do we handle rounding when 100.00 splits three ways?
- Should simplification minimize the number of transfers, or keep pairwise balances as recorded?
Core Requirements
- Create groups and add members.
- Record an expense with a payer, a total, and a split across participants.
- Support equal, exact, and percentage splits, validated to sum to the total.
- Show each member's net balance and pairwise debts within a group.
- Record settlements that reduce balances.
- Simplify group debts into a small set of suggested transfers.
Canonical class breakdown
Java sketchjava
// Java skeleton
enum SplitType { EQUAL, EXACT, PERCENT }
record Money(long minorUnits, String currency) {
public Money plus(Money o) { ... }
public Money minus(Money o) { ... }
}
class User {
private final String userId;
private final String displayName;
}
class Split {
private final User participant;
private final Money share;
}
class Expense {
private final String expenseId;
private final User paidBy;
private final Money total;
private final String description;
private final List<Split> splits; // sums exactly to total
private final String reversesExpenseId; // set on edit or delete
}
class Settlement {
private final String settlementId;
private final User from;
private final User to;
private final Money amount;
}
interface SplitStrategy {
// validates inputs, assigns the rounding remainder deterministically
List<Split> split(Money total, User payer, Map<User, BigDecimal> inputs);
}
class EqualSplit implements SplitStrategy { ... }
class ExactSplit implements SplitStrategy { ... }
class PercentSplit implements SplitStrategy { ... }
class BalanceLedger {
private final Map<User, Long> netBalance; // positive = owed money
private final Map<User, Map<User, Long>> pairwise; // debtor -> creditor -> amount
public void post(Expense e) { ... }
public void post(Settlement s) { ... }
public long netOf(User u) { ... }
}
record Transfer(User from, User to, Money amount) {}
class DebtSimplifier {
// greedy: match largest creditor with largest debtor until all are zero
public List<Transfer> simplify(Map<User, Long> netBalances) { ... }
}
class Group {
private final String groupId;
private final Set<User> members;
private final List<Expense> expenses; // append-only
private final List<Settlement> settlements;
private final BalanceLedger ledger;
}
class ExpenseService {
private final Map<SplitType, SplitStrategy> strategies;
private final DebtSimplifier simplifier;
public Expense addExpense(Group g, User payer, Money total, SplitType type, Map<User, BigDecimal> inputs) { ... }
public Expense editExpense(Group g, Expense old, Money total, SplitType type, Map<User, BigDecimal> inputs) { /* reversal + new entry */ }
public Settlement settle(Group g, User from, User to, Money amount) { ... }
public List<Transfer> suggestTransfers(Group g) { ... }
}
Key Decisions to Defend
- Money is a long in minor units plus a currency code; floating point drifts the moment 10.00 splits three ways.
- SplitStrategy is a Strategy, so a new split type (shares, adjustments) adds a class and leaves Expense untouched.
- Rounding remainders go to a deterministic participant, such as the payer, so every client computes identical balances.
- Editing an expense posts a reversal and a new entry; the ledger stays append-only and the history stays auditable.
- Simplification is a view over net balances; it suggests transfers and leaves the recorded expenses intact.
Likely follow-ups
- · Add multi-currency groups, when do you convert and at what rate?
- · Two members edit the same expense offline, how do you merge?
- · Is the greedy simplifier minimal? Show a case where it is not.
- · Support recurring expenses like rent without duplicating logic.
Dive Deeper
Expense sharing tests money handling and the separation of facts from derived views. Expenses and settlements are facts; balances and simplified transfers are derived from them, and strong candidates keep that line sharp so edits and deletes stay correct. Rounding is where many designs fail: a three-way split of 100.00 must assign the extra cent somewhere, deterministically. Interviewers also probe whether you know that finding the true minimum number of transfers is NP-hard, which is why the greedy match is the practical choice.