What The Code Does
The design is centered around a group ledger
User and Group membership
`User` represents identity. `GroupManager` owns the users inside one group and initializes each user's group balance at zero. Overriding `equals()` and `hashCode()` is important because users are map keys.
Expense creation
When an expense is added, the service asks the selected split strategy to produce a user-to-share map. The expense is then stored with paid-by, amount, and split details.
Balance update
The payer receives credit for the full amount. Every participant is debited by their share. The final number is net balance: positive means receive, negative means pay.
Pattern Analysis
Why these design patterns fit the Splitwise problem
Strategy Pattern
SplitStrategy, EqualSplit, PercentageSplit
The algorithm for calculating shares changes depending on the expense type. Equal and percentage split should not be hard-coded inside `GroupManager`; that would make the group class grow every time a new split type is introduced. Strategy keeps the calculation replaceable.
interface SplitStrategy {
Map<User, Double> splitAmount(double amount, List<User> users);
}
class EqualSplit implements SplitStrategy {
public Map<User, Double> splitAmount(double amount, List<User> users) {
double perUserAmount = amount / users.size();
Map<User, Double> result = new HashMap<>();
for (User user : users) {
result.put(user, perUserAmount);
}
return result;
}
}
Singleton Pattern
SplitwiseService
`SplitwiseService` is the application entry point for adding expenses and settling up. In this simplified in-memory version, a Singleton gives one shared coordinator. In production, this can become a dependency-injected service instead of a static Singleton.
class SplitwiseService {
private static SplitwiseService instance;
private SplitwiseService() {}
public static synchronized SplitwiseService getInstance() {
if (instance == null) {
instance = new SplitwiseService();
}
return instance;
}
}
Ledger Model
Group-wise balance tracking
The balance sheet is the core Splitwise logic. The balance sheet is not storing each transaction as debt edges. It stores net group balance per user, which makes profile and group summaries fast to display.
private void updateBalanceSheet(User paidBy, double amount, Map<User, Double> splitMap) {
balanceSheet.put(paidBy, balanceSheet.getOrDefault(paidBy, 0.0) + amount);
for (Map.Entry<User, Double> entry : splitMap.entrySet()) {
User user = entry.getKey();
double share = entry.getValue();
balanceSheet.put(user, balanceSheet.getOrDefault(user, 0.0) - share);
}
}
Thread Safety Boundary
synchronized group mutations
Expense addition, user removal, and settlement mutate shared group state. Marking these methods `synchronized` ensures a single group ledger update completes before another thread changes the same ledger. This protects the balance sheet from race conditions in this in-memory design.
public synchronized void addExpense(...) {
Map<User, Double> splitMap = splitStrategy.splitAmount(amount, users);
expenses.add(new Expense(expenseId, paidBy, amount, splitMap));
updateBalanceSheet(paidBy, amount, splitMap);
}
Execution Trace
How the balance changes in the sample
SAM pays 1000 equally
SAM gets credited 1000, then pays his own share 333.33. Net SAM: +666.67. PAM and Kris: -333.33 each.
Kris pays 4000 equally
Kris gets credited 4000 and owes 1333.33 share. His net gain is +2666.67, then it merges with old balance.
PAM pays 2000 by percentage
The strategy maps SAM to 10%, PAM to 40%, and Kris to 50%. This keeps custom split logic outside the group ledger.
PAM pays Kris 1000
PAM's balance moves up by 1000 because she paid. Kris's balance moves down by 1000 because he received.
Runnable Java
Full code from the walkthrough
Main.java
The compiler-ready code demonstrates equal split, percentage split, group-wise ledger, and settlement.
Embedded Compiler
Paste the Java source into the compiler and run the sample flow.
Design Review Notes
Good: Split calculation is separated from balance tracking.
Good: `User` is safe as a map key because identity methods are implemented.
Good: Group mutations are synchronized for this in-memory implementation.
Next: Add an Observer-based notification system when expenses and settlements occur.
Next: Add an `ExactSplit` strategy and a settlement optimizer for minimum transactions.
Interview Ready
Notes For Interview
How to explain the design
This Splitwise LLD is centered around a group ledger. Users join a group, expenses are added by one payer, and the selected split strategy calculates how much each participant owes. The group maintains a net balance sheet where positive means the user should receive, negative means the user should pay, and zero means the user is settled.
The key interview point is that split logic is not hard-coded inside the group. It is delegated to `SplitStrategy`, so Equal, Percentage, Exact, or Share split can be added without changing the core ledger update logic.
Flow Diagram
UML Diagram: IS-A and HAS-A
Core classes
- User: represents identity using `id` and `name`; equality is based on `id` because users are map keys.
- Expense: stores expense id, payer, amount, and calculated user-to-share map.
- GroupManager: owns users, expenses, balance sheet, expense updates, and settlement.
- SplitStrategy: abstracts split calculation through `EqualSplit` and `PercentageSplit`.
- SplitwiseService: acts as the application coordinator in this in-memory version.
Balance logic
When SAM pays `1000` for three people, each share is `333.33`. SAM gets credited with `1000`, then SAM, PAM, and Kris are each debited by `333.33`. Final balance: SAM receives `666.67`, PAM pays `333.33`, and Kris pays `333.33`.
During settlement, if PAM pays Kris `1000`, PAM's balance increases because her debt reduces, and Kris's balance decreases because his receivable reduces.
Design patterns
- Strategy Pattern: keeps split calculation replaceable and open for extension.
- Singleton Pattern: gives one shared `SplitwiseService` coordinator for the demo.
- Ledger Model: stores net balances instead of pairwise debt edges.
- Synchronized methods: protect in-memory group mutations from race conditions.
Why these patterns are used
The main changing behavior in Splitwise is how an expense is divided. Equal split, percentage split, exact split, and share split all produce the same output type: a map of user to amount owed. Strategy Pattern is used because the group should not contain `if-else` blocks for every split type. If a new split type is introduced, we add a new strategy class and the balance update logic remains untouched.
The ledger model is used because the UI usually needs to answer "how much does this user owe or receive?" quickly. Instead of storing every pairwise debt edge, we maintain one net value per user inside the group. This makes group summaries simple. Pairwise settlement can be derived later using a separate settlement optimization algorithm.
Algorithm explanation
The expense update algorithm has two phases. First, the split strategy calculates each participant's share. Second, the ledger is updated by crediting the payer with the full amount and debiting every user by their share. This works because the payer also participates in the split, so their final net balance becomes `amount paid - own share`.
Settlement uses a reverse adjustment. If a debtor pays a receiver, the payer's balance increases toward zero and the receiver's balance decreases toward zero. For minimum transactions, a production system can separate positive balances and negative balances, then greedily match the largest payer with the largest receiver until all balances become zero.
Strong interview answer
"I would model Splitwise at the group level. Each group has users, expenses, and a balance sheet. When an expense is added, the payer gets credited with the full amount and every participant gets debited by their share. Split calculation is delegated to a strategy, so new split types can be added without modifying the group ledger logic."
Production improvements
- Use `BigDecimal` or smallest currency unit instead of `double` for money.
- Add `ExactSplit`, `ShareSplit`, and a settlement optimizer for minimum transactions.
- Persist users, groups, expenses, and settlements in a database.
- Add access control so only group members can add expenses.
- Add Observer-based notifications when expenses or settlements occur.
- Replace Singleton with dependency injection in a production service.
Weakness to mention
The current implementation uses `double`, which is risky for money because of precision issues. Also, `PercentageSplit` checks that percentages sum to `100`, but it should more strictly validate that the percentage map matches the group members.