Designing Splitwise: the low-level design interview, explained
"Design Splitwise" is a favourite low-level design interview question because it looks friendly and turns sharp the moment you start modelling it. Everyone has split a dinner bill, so the domain needs no explanation — but the round is not testing whether you can divide a number by four. It is testing object-oriented design: can you turn "people share expenses and owe each other money" into a clean set of classes, make the split logic extensible when the interviewer asks for a percentage split five minutes in, and keep a correct running picture of who owes whom as expenses pile up?
This guide walks Splitwise the way a strong candidate works it in an LLD / OOD interview: clarify the requirements first, identify the core entities, sketch the classes and their key methods, and then defend the handful of decisions the interviewer always pushes on — how different kinds of split (equal, exact, percentage) stay polymorphic instead of a tangle of if statements, how balances are tracked so "who owes whom" is always answerable, and how you settle a whole group in the fewest possible transactions. In a low-level design round you write these classes out in an editor, not as a finished program but as a design you can explain and extend out loud, one method at a time.
Why Splitwise comes up in low-level design interviews
Splitwise earns its place because it packs a lot of modelling into a domain nobody needs explained. There are obvious entities (users, groups, expenses, balances), an obvious axis of variation (equal vs exact vs percentage splits) that begs for polymorphism, and an obvious algorithmic twist (settle the group with as few transfers as possible) that separates a candidate who stops at "store the balances" from one who can reason about the data. An interviewer can watch you make a dozen modelling decisions in half an hour and see exactly how you handle abstraction, validation and a small but real algorithm.
The interviewer rarely lets a tidy class diagram stand on its own. "Four friends on a trip rack up fifteen expenses, paid by different people, split different ways. At the end A owes B, B owes C, and C owes A. Walk me through how your balance data is stored, and then how you settle the whole group — because paying every raw debt back is fifteen transfers, and I want the fewest." That question — turning a web of pairwise debts into a minimal set of settlements — is the one a pretty diagram can never survive, and the one Splitwise is really testing once the basics are on the board.
The rounds on Zynter are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: you put a class on the board, and the next sentence out of the interviewer's mouth adds a requirement or attacks a seam — a new split type, a validation rule, or the demand to minimise settlements.
What the interviewer is really testing
A low-level design answer is read on the same four dimensions as any design round. Problem solving — did you model the splits with polymorphism and reason correctly about the settlement algorithm, or hard-code three split types and store balances naively? Communication — can you justify why Split is an abstract type and not a type string with a switch? Technical execution — do you handle validation (exact splits that don't sum to the total, percentages that don't reach 100) and the simplify-debts logic, or only the equal-split happy path? Time management — did you clarify and prioritise before coding, and spend your half hour on the split model and settlement rather than perfecting a getter? Reading this guide teaches the design; it does not teach you to defend it while someone says "now make it a percentage split that doesn't add up to 100".
Step 1: Clarify the requirements
The single biggest mistake in an LLD round is writing a class before asking a question. Spend the first two minutes scoping out loud — it is graded, and it stops you building the wrong thing.
Functional requirements to confirm:
- Users can belong to one or more groups, and can also share expenses one-to-one outside a group.
- A user adds an expense: a total amount, who paid it (one payer to start), and how it is split among a set of participants.
- Split types: equal (divide evenly), exact (each participant owes a named amount), and percentage (each owes a share that sums to 100%).
- The system tracks balances — for any pair, who owes whom and how much — and can show each user's net balance.
- Users can settle up, and the system can simplify a group's debts into the fewest transactions.
Non-functional / constraints to surface (this is where senior candidates separate themselves):
- Extensibility — new split types should be addable without touching the expense or balance code.
- Validation — exact splits must sum to the expense total; percentage splits must sum to 100; reject invalid expenses rather than corrupting balances.
- Scope boundaries — are we handling multiple payers, currencies, payment integration, notifications? Agree what is in and out, out loud, so you are not marked down for "missing" something you deliberately deferred.
State your assumptions explicitly: "I'll assume a single payer per expense, one currency, and I'll stub notifications — tell me if you want multi-payer or currency conversion in scope." That one sentence shows you prioritise, which is itself part of the score.
Step 2: Identify the core entities
With requirements agreed, name the classes before writing any. The nouns in the problem are your first draft of the object model:
-
User— an id, name, contact; the atom that owes or is owed. -
Group— a named set of users who share expenses (and a view of balances within it). -
Expense— a total amount, thepaidByuser, and a list ofSplitobjects describing who owes what. -
Split— an abstract base linking a user to the amount they owe for an expense, with concreteEqualSplit,ExactSplitandPercentSplitsubclasses (the percentage one carries apercentfield). -
BalanceSheet— the running record of who owes whom, typically a directed mapMap<userId, Map<userId, amount>>. -
ExpenseManager(theSplitwisecontroller) — the entry point:addExpense,getBalances,simplifyBalances,settleUp.
Naming the relationships out loud matters as much as the classes: a Group has many Users, an Expense has one payer and has many Splits, each Split references one User, and the ExpenseManager owns the BalanceSheet. That "has-a" vocabulary, plus the Split "is-a" hierarchy, is what an interviewer listens for.
Step 3: Sketch the classes
Now write the classes — as skeletons you can talk through, not a finished program. Lead with the Split hierarchy and the manager, because they carry the design decisions.
abstract class Split {
protected User user;
protected double amount; // what this user owes for the expense
public abstract boolean isValid(double total);
}
class EqualSplit extends Split { /* amount set by the manager: total / n */ }
class ExactSplit extends Split {
public boolean isValid(double total) { return amount >= 0; }
}
class PercentSplit extends Split {
private double percent; // amount derived from percent of total
public boolean isValid(double total) { return percent >= 0 && percent <= 100; }
}
class Expense {
private String id;
private double amount;
private User paidBy;
private List<Split> splits; // must reconcile with amount
}
class ExpenseManager {
// balance.get(a).get(b) = how much a owes b
private Map<String, Map<String, Double>> balance = new HashMap<>();
public void addExpense(Expense e) {
validate(e); // exact sums to total; percents sum to 100
for (Split s : e.getSplits()) {
if (s.getUser().equals(e.getPaidBy())) continue;
// the split's user owes the payer s.amount
adjust(s.getUser().getId(), e.getPaidBy().getId(), s.getAmount());
}
}
private void adjust(String from, String to, double amt) {
balance.computeIfAbsent(from, k -> new HashMap<>())
.merge(to, amt, Double::sum);
// optionally net against the reverse edge so A<->B collapses to one direction
}
public List<Transfer> simplifyBalances() {
// 1. net each user to a single number (paid - owed)
// 2. heap of creditors (+) and debtors (-)
// 3. repeatedly settle max creditor against max debtor
return greedySettle(netBalances());
}
}
Talk through the choices as you write them: each split type validates itself (isValid), so a new split type is a new class with its own rule, not another branch in the manager; the balance map is directional so "who owes whom" is a direct lookup; and simplifyBalances works on net balances, not the raw pairwise edges, which is what makes the minimal-transaction settlement possible.
Step 4: The design decisions interviewers probe
The diagram is the easy half. The follow-ups live in a handful of decisions, and having an answer ready for each is what the round rewards.
-
Polymorphic splits (the Strategy/inheritance call) — resist a
splitTypestring with aswitchin the manager. MakeSplitabstract withEqualSplit,ExactSplit,PercentSplit, each owning its ownisValidand amount derivation. When the interviewer says "add a split by shares (2:1:1)," you add aShareSplitclass and touch nothing else — the open/closed principle in action. -
Validation — the expense is only accepted if its splits reconcile: exact amounts must sum to the total, percentages must sum to 100 (within a rounding epsilon), and no negative shares. Validate in
addExpensebefore mutating any balance, and return a clear error; a half-applied expense that corrupts the balance sheet is the bug they are hunting for. -
Balance tracking — a directed map
balance[a][b]= how muchaowesb. On each expense, every non-payer participant's debt to the payer is added; optionally net the reverse edge so anA→BandB→Acollapse to a single direction. This keeps per-pair balances O(1) to read and update. - Simplify debts — the fewest transactions — this is the follow-up from the opening scenario. Paying back every raw pairwise edge is wasteful; instead compute each person's net balance (total paid minus total owed), put positive net balances (creditors) in a max-heap and negative ones (debtors) in another, then repeatedly take the largest creditor and largest debtor and settle the smaller of the two amounts between them, pushing the remainder back. The result settles the group in at most n−1 transfers. State the trade-off out loud: this minimises the number of transfers but no longer mirrors who originally paid for what — some groups prefer to preserve the real pairwise debts, so I'd make simplification an explicit action, not automatic.
- Edge cases — floating-point rounding (store cents as integers, or reconcile the last split to absorb the remainder), a participant who is also the payer, and settling a partial amount.
Low-Level Design
LLD · Splitwise
Design Splitwise and defend the simplify-debts settlement out loud
Design patterns that fit Splitwise
You do not need to force patterns in, but naming the ones that fit — and why — is a strong signal in an OOD round.
- Strategy — the split types. Treating each split as a strategy (or a polymorphic subclass) lets the way an expense divides vary independently of the expense and the balance logic. The cleanest, most reused idea in this problem.
-
Factory — split creation. A
SplitFactorythat turns a request ("equal", "percent", with its parameters) into the rightSplitsubclass keeps construction logic out of the manager. - Observer — notifications. Users subscribed to a group can be notified when an expense is added or a settlement happens, without the manager knowing about delivery channels.
-
Singleton — the
ExpenseManageris often a single shared instance; note the trade-off that a singleton can complicate testing and must be thread-safe if expenses are added concurrently.
Say it crisply: Strategy for the split types is the one that pays off most; Factory keeps split creation tidy; Observer handles notifications; Singleton models the single manager but I'd keep it injectable for tests. Naming the trade-off, not just the pattern, is what reads as experience.
Low-Level Design
LLD · Core skills
Apply Strategy, Factory and Observer out loud
How to talk about a low-level design in an interview
The probes from every section rhyme. Master these five and you can defend any LLD, not just Splitwise.
- "What are the core classes here?" → Name the nouns — User, Group, Expense, the Split hierarchy, BalanceSheet, ExpenseManager — and the relationships (has-a, is-a) before writing a line. (communication)
- "How do you handle the different split types?" → An abstract Split with EqualSplit, ExactSplit and PercentSplit subclasses that each validate themselves, so a new split type is a new class, not a branch in the manager — the open/closed principle. (problem solving)
- "Where do you validate an expense?" → In addExpense, before mutating any balance: exact amounts sum to the total, percentages sum to 100, no negatives; reject invalid rather than half-applying it. (technical execution)
- "Settle the group in the fewest transactions — how?" → Net each person to a single balance, put creditors and debtors in two heaps, and repeatedly settle the largest creditor against the largest debtor, which lands in at most n−1 transfers; I'd make it an explicit action because it loses the original who-paid-whom detail. (technical execution)
- "What breaks on the unhappy path?" → Floating-point rounding (store cents as integers or reconcile the last split), the payer appearing in the splits, and partial settlements. (time management)
Each maps to a debrief dimension. Reading them teaches the answers; it does not teach you to deliver them while someone says "now add a share-based split" — that only comes from saying them out loud.
How to prepare: a short plan
You do not need to memorise one reference solution. You need the OOD process automatic, so any "design X" prompt becomes requirements → entities → classes → decisions.
- Day 1: Drill the process on Splitwise — clarify, list entities, sketch the Split hierarchy and the balance map — until you can get a clean diagram on the board in ten minutes.
- Day 2: Drill the decisions — polymorphic splits and their validation, the directed balance map, and especially the simplify-debts algorithm with its two heaps. These are where the follow-ups live.
- Day 3: Do it out loud, timed, with someone adding a requirement mid-answer (a share-based split, multiple payers, currencies), then move to a second LLD (parking lot, elevator) to prove the process transfers.
This explainer feeds the broader low-level design interview track — Splitwise is a classic OOD problem, and the process it teaches transfers directly to the parking lot, the elevator and the rest.
Frequently asked questions
What is a Splitwise low-level design interview actually testing?
It is testing object-oriented design, not arithmetic. There is no puzzle to crack or test cases to pass; instead the interviewer watches how you turn a familiar problem into a clean set of classes, how you model the different split types without a tangle of conditionals, how you keep a correct running record of who owes whom, and whether you can handle the small but real algorithm of settling a group in the fewest transactions. Splitwise is popular precisely because everyone understands the domain, so the entire conversation is about the quality of your model — your use of abstraction for splits, your validation, your balance representation, and your ability to justify each decision out loud.
What are the main classes in a Splitwise design?
A standard model has User as the atom that owes or is owed, Group as a set of users sharing expenses, and Expense carrying a total amount, a paidBy user, and a list of Split objects. Split is an abstract base with EqualSplit, ExactSplit and PercentSplit subclasses, each linking a user to the amount they owe and validating itself. A BalanceSheet (typically a directed map of who owes whom) tracks balances, and an ExpenseManager is the controller exposing addExpense, getBalances, simplifyBalances and settleUp. The key is to state the relationships — a group has users, an expense has one payer and many splits — not just list the classes.
How do you handle different split types in Splitwise?
You use polymorphism rather than a type flag with a switch. Split is an abstract class, and EqualSplit, ExactSplit and PercentSplit are subclasses that each know how to derive their amount and validate themselves — an exact split checks its amount is non-negative, a percentage split checks its percent is between 0 and 100, and the manager checks that exact amounts sum to the total and percentages sum to 100. This is the open/closed principle at work: when the interviewer asks for a new kind of split, such as a share-based 2:1:1, you add a new subclass and touch none of the existing expense or balance code, which is exactly the extensibility the round rewards.
How does the simplify-debts algorithm work?
The goal is to settle a group with the fewest transfers rather than paying back every raw pairwise debt. First you compute each person's net balance — total they paid minus total they owe — so each user becomes a single positive number (a creditor) or negative number (a debtor). You put creditors in one max-heap and debtors in another, then repeatedly take the largest creditor and the largest debtor, settle the smaller of the two amounts between them, and push any remainder back onto its heap. This settles the whole group in at most n−1 transactions. The trade-off worth stating is that minimising transfers no longer reflects who originally paid for what, so many implementations make simplification an explicit action the user triggers rather than something applied automatically.
Which design patterns are used in Splitwise?
The most valuable is the Strategy pattern (realised as the polymorphic Split hierarchy) for the different split types, which lets the way an expense divides vary independently of the rest of the system. A Factory pattern fits creating the correct Split subclass from a request, keeping construction logic out of the manager. An Observer pattern suits notifications, so users are told when an expense is added or a settlement happens without the manager knowing about delivery channels. The ExpenseManager is often a Singleton because it represents one shared controller, though you should note that a singleton can complicate testing and must be thread-safe if expenses are added concurrently. The point in an interview is to name the pattern and its trade-off, not to force patterns in where they add no value.
How do I practise a low-level design interview out loud?
Reading a Splitwise solution is not the same as defending your split hierarchy and settlement algorithm while an interviewer adds a share-based split or pushes on the fewest-transactions question. On Zynter, the LLD track runs live voice rounds modelled on how real low-level design loops run — you write your classes and methods in the editor and a voice interviewer follows up one question at a time ("how do you handle a percentage split that doesn't sum to 100?", "settle these four people in the fewest transfers"), then gives you a debrief on problem solving, communication, technical execution and time management. The score is a practice signal, not a hiring decision.
Practise it on Zynter
You can draw the class diagram. The round is won on defending the fewest-transactions settlement out loud.
Pick the Expense Sharing (Splitwise) low-level design round, write your classes in the editor, and defend them out loud to a voice interviewer that keeps asking "where do you validate a percentage split?" and "settle these four friends in the fewest transfers — walk me through it" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the low-level design track (Sep 2026).
Practise the Splitwise LLD round →
Browse all interview tracks




Top comments (0)