Back🏗️

Expense Splitting (LLD)

AWAKENING0 / 100
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]0%

Expense Splitting (LLD)

Low Level Design·advanced·~20 min read

  • lld
  • expense-splitting
  • greedy
  • heap
  • concurrency
  • design
  • algorithms

What you'll learn

  • Problem Statement

    Design the backend for a group expense sharing application. Users create groups, add shared expenses, and the system computes the minimum number of transactions needed to settle all debts. Core challenges: - Efficient debt simplification…

  • Functional Requirements

    1. User management: Create accounts, manage profiles 2. Group management: Create groups, add/remove members 3. Expense tracking: Add expenses with flexible splitting 4. Balance computation: Real-time net balances per user 5. Settlement:…

  • Non-Functional Requirements

    - Consistency: Balance calculations must be exactly correct - Availability: Users can always view/add expenses - Latency: Balance queries , wrap in a domain object: Benefits: - Encapsulates merge logic (add to existing balance) - Returns…

  • Expense: Immutable Once Created

  • PaymentGraph: Settlement Result

    ---

  • Settlement Algorithm: Greedy Two-Heap

  • Why This Algorithm?

    The minimum transaction problem is NP-hard in general (reducible to Subset Sum). However, the greedy two-heap approach gives an excellent approximation: - Guarantees ≤ N-1 transactions (where N = users with non-zero balance) - Often prod…

  • Implementation

  • Correctness Proof Sketch

    1. Conservation: Total positive = Total negative (expenses sum to zero) 2. Progress: Each iteration eliminates at least one user from a heap 3. Termination: At most N-1 iterations (one user eliminated per step) 4. Completeness: When heap…

  • Summing Expenses

    Before running the settlement algorithm, sum all expenses in a group: ---

  • Simplified vs Transitive Settlement

  • The Stranger Problem

  • Transitive Settlement (Alternative)

    Instead of computing net balance per USER, compute per USER PAIR: More transactions (3 vs 2) but preserves social relationships.

  • Handling Pair Conflicts

    If in a later expense, D owes B $30: If pair values are negative, reverse the direction:

  • Design Decision

    Make this a group setting: ---

  • Concurrency Deep Dive

  • The Problem

    Two users add expenses to the same group simultaneously: 1. User A reads current balances: {X: +50, Y: -50} 2. User B reads current balances: {X: +50, Y: -50} 3. User A adds expense, writes: {X: +70, Y: -70} 4. User B adds expense, write…

  • Solution 1: Read-Write Lock

    Characteristics: - Multiple readers can proceed in parallel - Writer gets exclusive access - Readers and writer are mutually exclusive - Best when reads >> writes

  • Solution 2: Immutable Snapshot (Preferred for this system)

    Why this works: - Expenses are immutable once created (append-only) - Balance is derived from current expense list at read time - No lost updates possible (we're not updating balances, we're adding expenses) - ConcurrentHashMap.compute()…

  • Solution 3: Database-Level (Production)

    No concurrency issue at DB level because: - Expense insertion is independent (different rows) - Balance query is a SUM over all rows (sees whatever's committed) - Only need transaction isolation for the INSERT (prevent duplicate expenses…

  • Caching Architecture

  • What to Cache

  • Cache IDs, Not Objects

  • Invalidation Strategy

    | Event | Invalidate | |-------|-----------| | New expense added | groupexpenses:{groupId} | | User removed from group | groupmembers:{groupId} | | User profile updated | userprofile:{userId} | | Expense deleted | groupexpenses:{groupId}…

  • Database Design (Production)

  • Schema

  • Query Patterns

  • Indexing Strategy

    | Query Pattern | Index | |--------------|-------| | Expenses by group | idxexpensesgroup on expenses(groupid) | | Splits by user | idxsplitsuser on expensesplits(userid) | | Splits by expense | Already PK (expenseid, userid) | | Recent…

  • Service Layer Design

  • GroupService (Coordinator)

  • ExpenseService (Core Logic)

    ---

  • Testing Strategy

  • Unit Tests

  • Edge Cases to Test

    | Case | Expected Behavior | |------|-------------------| | All balances zero | Empty payment graph | | Single user owes everyone | N-1 transactions | | Two users, one expense | Single transaction | | Same amount owed and owing | Zero ne…

  • Scalability Considerations

  • For Large Groups (100+ members)

    | Concern | Solution | |---------|----------| | Expense list grows unbounded | Paginate, archive settled periods | | Balance computation slow | Pre-compute and cache net balances | | Many concurrent writers | Append-only expense store |…

  • For Many Groups per User

    - Partition expenses by groupid (natural shard key) - User's cross-group balance = fan-out query to each group shard - Consider materialized view for "user dashboard" showing all groups

  • For Real-Time Updates

    - When User A adds expense → push notification to group members - WebSocket for real-time balance updates on group page - Settlement graph recomputed on each page load (cheap for < 50 members) ---

  • Common Interview Mistakes

    1. Storing the payment graph: It's derived data — compute it, don't store it 2. Using double for money: Instant red flag. Always BigDecimal 3. Forgetting conservation: Sum of splits MUST equal zero 4. Ignoring the stranger problem: Discu…

← back to Low Level Design

Expense Splitting (LLD) — Low Level Design | GoCrack | GoCrack