← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›LLD Interview Prep›LLD
💸

Design Splitwise

Expense sharing with equal/exact/percent splits and balance sheet — popular at startups, fintech, and Amazon/Flipkart “design an app” LLD prompts.

📊 Medium → Hard⏱ 40–50 min🏢 Amazon · Flipkart · Paytm · Razorpay · startup fintech LLD
🎯 Interview goal

ExpenseService records who paid and how cost is split via SplitStrategy. Maintain net balance per user in a group; optional settle-up minimizes cash flow (mention as extension).

📋

The interview question

“Design an expense sharing app like Splitwise.” Users in a group add expenses; system tracks who owes whom.

Core: add expense, split logic, view balances — not payment gateway integration in v1.

💡
Clarify split types (equal, exact amounts, percent) — implement equal first, interface for others.
❓

Clarifying questions (ask before coding)

1Split types: equal, exact, percentage?
Why askStrategy per type; equal is minimum bar.
2One group or user in many groups?
Why askModel Group with member list; balances scoped per group.
3Settle-up / simplify debts?
Why askGreedy min-cash-flow is extension — net balance map is v1.
4Currency / multi-currency?
Why askUsually out of scope unless fintech loop.
✅

Requirements & assumptions (state aloud)

  • addExpense(paidBy, amount, participants, splitType) updates balances.
  • getBalances(groupId) → who owes whom (net per user).
  • Equal split: amount / n; handle rounding (pennies to payer).
  • v1: in-memory, single process.
🧩

High-level design

On expense: payer credit += amount; each participant debit += share. Net map user → balance (positive = others owe them).

SplitStrategy.split(total, users) returns Map<user, share>.

  1. User, Group, Expense, ExpenseSplit
  2. SplitStrategy + EqualSplitStrategy
  3. ExpenseService, BalanceSheet
Splitwise flow
Expense → strategy → balance sheet
📐

UML class diagram (optional)

Emphasize Strategy for split algorithms and facade ExpenseService.

Splitwise UML
Facade + strategy for splits
🎨

Design patterns used (and why)

PatternWhereWhy (interview soundbite)
🎯StrategySplitStrategyEqual / exact / percent without changing ExpenseService.
🪞FacadeExpenseServiceSingle API for add expense + get balances.
💻

Full solution — domain & split strategy

1. User, group, split strategyjava
public class User {
    private final String userId;
    private final String name;

    // Person who pays or owes in a group
    public User(String userId, String name) {
        this.userId = userId;
        this.name = name;
    }

    // Stable id used as key in balance and split maps
    public String getUserId() { return userId; }
}

public class Group {
    private final String groupId;
    private final List<User> members = new ArrayList<>();

    // Expense sharing circle (trip, apartment, etc.)
    public Group(String groupId) { this.groupId = groupId; }

    // Adds a member to the group
    public void addMember(User u) { members.add(u); }

    // Read-only member list for split calculations
    public List<User> getMembers() { return Collections.unmodifiableList(members); }

    // Id scopes balance sheet per trip/household
    public String getGroupId() { return groupId; }
}

public interface SplitStrategy {
    // Divides total amount into per-user shares (sum should equal total)
    Map<String, BigDecimal> split(BigDecimal total, List<User> participants);
}

public class EqualSplitStrategy implements SplitStrategy {
    // Each participant owes total / n
    @Override
    public Map<String, BigDecimal> split(BigDecimal total, List<User> participants) {
        if (participants.isEmpty()) return Map.of();
        int n = participants.size();
        BigDecimal share = total.divide(BigDecimal.valueOf(n), 2, RoundingMode.HALF_UP);
        Map<String, BigDecimal> out = new HashMap<>();
        for (User u : participants) out.put(u.getUserId(), share);
        return out;
    }
}
⚙️

Full solution — expense service & balances

2. Expense & balance sheetjava
public class Expense {
    private final String expenseId = UUID.randomUUID().toString();
    private final String groupId;
    private final String paidByUserId;
    private final BigDecimal amount;
    private final Map<String, BigDecimal> splits;

    // Immutable record of one spend and how it was split
    public Expense(String groupId, String paidByUserId, BigDecimal amount, Map<String, BigDecimal> splits) {
        this.groupId = groupId;
        this.paidByUserId = paidByUserId;
        this.amount = amount;
        this.splits = Map.copyOf(splits);
    }
}

public class BalanceSheet {
    private final Map<String, BigDecimal> net = new HashMap<>();

    // Credits payer by sum of splits; debits each participant their share
    public void applyExpense(String paidBy, Map<String, BigDecimal> splits) {
        net.merge(paidBy, sumValues(splits), BigDecimal::add);
        for (var e : splits.entrySet())
            net.merge(e.getKey(), e.getValue().negate(), BigDecimal::add);
    }

    // Sums all split amounts (should equal expense total)
    private static BigDecimal sumValues(Map<String, BigDecimal> m) {
        return m.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    // Returns net balance per userId (+ means others owe them)
    public Map<String, BigDecimal> getBalances() {
        return Collections.unmodifiableMap(net);
    }
}
3. ExpenseService + demojava
public class ExpenseService {
    private final Map<String, BalanceSheet> groupBalances = new HashMap<>();
    private final SplitStrategy defaultSplit = new EqualSplitStrategy();

    // Splits expense equally and updates group balance sheet
    public void addExpense(Group group, User paidBy, BigDecimal amount, List<User> participants) {
        Map<String, BigDecimal> splits = defaultSplit.split(amount, participants);
        BalanceSheet sheet = groupBalances.computeIfAbsent(group.getGroupId(), k -> new BalanceSheet());
        sheet.applyExpense(paidBy.getUserId(), splits);
    }

    // Returns who owes whom for a group (net balances)
    public Map<String, BigDecimal> getBalances(String groupId) {
        return groupBalances.getOrDefault(groupId, new BalanceSheet()).getBalances();
    }
}

public class SplitwiseDemo {
    // Alice pays 100 split with Bob — print resulting balances
    public static void main(String[] args) {
        User alice = new User("u1", "Alice");
        User bob = new User("u2", "Bob");
        Group trip = new Group("g1");
        trip.addMember(alice);
        trip.addMember(bob);

        ExpenseService svc = new ExpenseService();
        svc.addExpense(trip, alice, new BigDecimal("100"), List.of(alice, bob));
        System.out.println(svc.getBalances("g1")); // bob owes alice
    }
}
🎤

While you code — questions interviewers ask

🚀

Extensions & follow-ups

  • Minimize transactions (graph / greedy settle).
  • ExactSplitStrategy and PercentSplitStrategy.
  • Activity feed (Observer) on new expense.
⚠️

Common mistakes (fail signals)

  • Storing pairwise owed matrix without net simplification (OK v1 but explain scale).
  • No validation that splits sum to total.
  • Hard-coded equal split only with no Strategy interface.
🧾

60-second cheat sheet

CoreExpenseService, SplitStrategy, BalanceSheet
Flowsplit → payer +total, each user -share
PatternsStrategy (split), Facade (service)
ExtendSettle-up, exact/percent splits
🔗

More LLD problems