THN Interview Prep

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

EntityResponsibilityKey Attributes
UserParticipantidentifier, displayName
GroupExpense containeridentifier, name, members
ExpenseOne bill eventidentifier, amount, payer, splits, createdAt
SplitLineOne person's sharememberId, amountOrWeight
SplitStrategyComputes shareskind (equal, percent, exact)
LedgerEntryBalances after expensedebit, credit, reference
SettlementPayment between two userspayer, payee, amount, timestamp

3. Class Diagram

Loading diagram…

4. State / Sequence Diagram (where relevant)

Loading diagram…

5. Design Patterns Applied

  • Strategy — Pluggable SplitStrategy for equal, percent, shares. Strategy pattern.
  • Command — Record expense as command object for undo/compensation flows. Command pattern.
  • FacadeGroupBalanceSheet hiding 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 Group or optimistic concurrency with version field on group row in DB.
  • Go: sync.Mutex on hot Group struct 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?

On this page