Design Splitwise (LLD)
1. Requirements
-
Functional
- Users create groups and add expenses split equally, by percent, or by exact amounts.
- Track who paid and how much each member owes or is owed; simplify debts into fewer edges (optional feature).
- List balances per user and per group; settle up records a payment between two users.
-
Non-Functional
- Auditability: every expense is immutable; corrections via reversing entries.
- Extensible split strategies without changing expense aggregate root.
-
Assumptions / Out of Scope
- Single currency per group in core model; FX as separate service.
- Authentication and notifications omitted.
2. Core Entities
| Entity | Responsibility | Key Attributes |
|---|---|---|
| User | Participant | identifier, displayName |
| Group | Expense container | identifier, name, members |
| Expense | One bill event | identifier, amount, payer, splits, createdAt |
| SplitLine | One person's share | memberId, amountOrWeight |
| SplitStrategy | Computes shares | kind (equal, percent, exact) |
| LedgerEntry | Balances after expense | debit, credit, reference |
| Settlement | Payment between two users | payer, payee, amount, timestamp |
3. Class Diagram
Loading diagram…
4. State / Sequence Diagram (where relevant)
Loading diagram…
5. Design Patterns Applied
- Strategy — Pluggable
SplitStrategyfor equal, percent, shares. Strategy pattern. - Command — Record expense as command object for undo/compensation flows. Command pattern.
- Facade —
GroupBalanceSheethiding ledger complexity from UI. Facade pattern.
6. Implementation
Go
package splitwise
type User struct {
Identifier string
DisplayName string
}
type SplitLine struct {
MemberID string
Amount int64
}
type Expense struct {
Identifier string
TotalCents int64
PayerID string
Lines []SplitLine
}
type Group struct {
Identifier string
Members []*User
Expenses []*Expense
}
func (group *Group) AddExpense(expense Expense) error { /* validates splits sum to total */ }JavaScript
class Expense {
constructor({ identifier, totalCents, payerId, splitLines }) {
this.identifier = identifier;
this.totalCents = totalCents;
this.payerId = payerId;
this.splitLines = splitLines;
}
}
class Group {
constructor({ identifier, name, members }) {
this.identifier = identifier;
this.name = name;
this.members = members;
this.expenses = [];
}
addExpense(expense) { /* ... */ }
}7. Concurrency / Thread Safety
- Collisions: Concurrent expense adds mutating balance aggregates.
- Granularity: Mutex per
Groupor optimistic concurrency with version field on group row in DB. - Go:
sync.Mutexon hotGroupstruct for in-memory demo; production uses transactions.
8. Extensibility & Followups
- Graph simplification (min cash flow) as pure function over net balances.
- Multi-currency with normalized amounts in base currency per day rate.
- Itemized receipts as child lines referencing catalog items.
- Edge cases: rounding remainder assignment, deleted members, disputed expenses.
Last updated on
Spotted something unclear or wrong on this page?