Skip to main content
System DesignDSA & CodingIndia Tech Careers

Design Splitwise: Machine Coding Round Walkthrough

A complete walkthrough of the Splitwise / expense-sharing machine coding problem: requirements, entities, a Strategy-based split design, balance tracking, debt simplification, runnable Java code, and the follow-up questions interviewers ask.

InterviewPilot TeamNovember 6, 20266 min read

The expense-sharing app is a classic machine coding problem: small enough to build in 90 minutes, rich enough to show whether you can design extensible classes. This walkthrough follows the time-boxed workflow from our LLD and machine coding guide, with runnable Java at the end.

Step 1: Clarify requirements (10 minutes)

Ask, then write down what's in scope:

  • Users can add an expense paid by one user and shared among several.
  • Split types: equal, exact amounts, percentages.
  • Show balances: for one user, and for everyone.
  • Out of scope unless asked: groups, multiple currencies, persistence, settling up via payments.

Typical input in the problem statement looks like:

EXPENSE u1 1000 4 u1 u2 u3 u4 EQUAL
EXPENSE u1 1250 2 u2 u3 EXACT 370 880
EXPENSE u4 1200 4 u1 u2 u3 u4 PERCENT 40 20 20 20
SHOW
SHOW u1

Confirm the output format too, for example "u2 owes u1: 250".

Step 2: Entities and extension points (10 minutes)

EntityResponsibility
Userid, name
Splita participant and their share
SplitStrategyturns an amount + inputs into a list of shares; validates them
Expensepayer, amount, the computed splits
BalanceSheetpairwise balances; updated per expense
ExpenseServiceentry point: builds expenses, updates balances

The extension point is the split rule. A new rule, such as splitting by shares ("2 parts for me, 1 for you"), should be one new class with no edits elsewhere. Say this aloud; it's what interviewers are listening for.

Step 3: Data model for balances

Use a map of maps: balance[a][b] > 0 means a owes b that amount. On every expense paid by p, each participant u ≠ p with share s:

  • balance[u][p] += s
  • balance[p][u] -= s

Keeping both directions makes "show balances for u1" a single lookup.

Store money as integers in paise to avoid floating-point errors.

Step 4: The code (40 minutes)

import java.util.*;
 
interface SplitStrategy {
    /** Returns each participant's share in paise; must sum to amount. */
    Map<String, Long> split(long amount, List<String> users, List<Double> values);
}
 
class EqualSplit implements SplitStrategy {
    public Map<String, Long> split(long amount, List<String> users, List<Double> values) {
        long base = amount / users.size(), rem = amount % users.size();
        Map<String, Long> out = new LinkedHashMap<>();
        for (String u : users) out.put(u, base + (rem-- > 0 ? 1 : 0));
        return out;
    }
}
 
class ExactSplit implements SplitStrategy {
    public Map<String, Long> split(long amount, List<String> users, List<Double> values) {
        Map<String, Long> out = new LinkedHashMap<>();
        long sum = 0;
        for (int i = 0; i < users.size(); i++) {
            long v = Math.round(values.get(i) * 100);
            out.put(users.get(i), v);
            sum += v;
        }
        if (sum != amount) throw new IllegalArgumentException("exact shares must add up to the total");
        return out;
    }
}
 
class PercentSplit implements SplitStrategy {
    public Map<String, Long> split(long amount, List<String> users, List<Double> values) {
        double pct = values.stream().mapToDouble(Double::doubleValue).sum();
        if (Math.abs(pct - 100.0) > 1e-9) throw new IllegalArgumentException("percentages must add up to 100");
        Map<String, Long> out = new LinkedHashMap<>();
        long assigned = 0;
        for (int i = 0; i < users.size(); i++) {
            long v = (i == users.size() - 1) ? amount - assigned
                                             : Math.round(amount * values.get(i) / 100.0);
            out.put(users.get(i), v);
            assigned += v;
        }
        return out;
    }
}
 
class BalanceSheet {
    private final Map<String, Map<String, Long>> owes = new TreeMap<>();
 
    void record(String payer, Map<String, Long> shares) {
        shares.forEach((user, share) -> {
            if (user.equals(payer) || share == 0) return;
            add(user, payer, share);
            add(payer, user, -share);
        });
    }
 
    private void add(String a, String b, long v) {
        owes.computeIfAbsent(a, k -> new TreeMap<>()).merge(b, v, Long::sum);
    }
 
    List<String> lines(String onlyUser) {
        List<String> out = new ArrayList<>();
        owes.forEach((a, row) -> row.forEach((b, v) -> {
            if (v > 0 && (onlyUser == null || onlyUser.equals(a) || onlyUser.equals(b)))
                out.add(a + " owes " + b + ": " + String.format("%.2f", v / 100.0));
        }));
        return out.isEmpty() ? List.of("No balances") : out;
    }
 
    Map<String, Long> netPerUser() {
        Map<String, Long> net = new TreeMap<>();
        owes.forEach((a, row) -> row.forEach((b, v) -> net.merge(a, -v, Long::sum)));
        return net; // positive = should receive, negative = should pay
    }
}
 
class ExpenseService {
    private final Map<String, SplitStrategy> strategies = Map.of(
        "EQUAL", new EqualSplit(), "EXACT", new ExactSplit(), "PERCENT", new PercentSplit());
    final BalanceSheet sheet = new BalanceSheet();
 
    void addExpense(String payer, double amount, List<String> users, String type, List<Double> values) {
        SplitStrategy s = strategies.get(type);
        if (s == null) throw new IllegalArgumentException("unknown split type " + type);
        if (users.isEmpty()) throw new IllegalArgumentException("no participants");
        sheet.record(payer, s.split(Math.round(amount * 100), users, values));
    }
}
 
public class Main {
    public static void main(String[] args) {
        ExpenseService svc = new ExpenseService();
        svc.addExpense("u1", 1000, List.of("u1", "u2", "u3", "u4"), "EQUAL", List.of());
        svc.addExpense("u1", 1250, List.of("u2", "u3"), "EXACT", List.of(370.0, 880.0));
        svc.addExpense("u4", 1200, List.of("u1", "u2", "u3", "u4"), "PERCENT", List.of(40.0, 20.0, 20.0, 20.0));
        svc.sheet.lines(null).forEach(System.out::println);
    }
}

Running it prints balances like u2 owes u1: 620.00 and u1 owes u4: 230.00. Walk the interviewer through one expense by hand to show the numbers are right.

Step 5: Edge cases (15 minutes)

  • Unknown split type or user: fail with a clear message.
  • Shares that don't add up: rejected by the strategy, in one place.
  • Rounding: paise as integers; remainder handed out deterministically.
  • Payer not among participants: valid. The payer just isn't charged.
  • Zero or negative amounts: reject.

Follow-up: simplify debts

"Minimise the number of transactions to settle up" is the classic follow-up.

  1. Compute each user's net balance (sum of what they're owed minus what they owe).
  2. Put creditors in a max-heap and debtors in a max-heap by absolute amount.
  3. Repeatedly match the biggest debtor with the biggest creditor, settle the smaller of the two amounts, and push back any remainder.
static List<String> simplify(Map<String, Long> net) {
    PriorityQueue<Map.Entry<String, Long>> cred = new PriorityQueue<>((x, y) -> Long.compare(y.getValue(), x.getValue()));
    PriorityQueue<Map.Entry<String, Long>> debt = new PriorityQueue<>((x, y) -> Long.compare(y.getValue(), x.getValue()));
    net.forEach((u, v) -> {
        if (v > 0) cred.add(new AbstractMap.SimpleEntry<>(u, v));
        else if (v < 0) debt.add(new AbstractMap.SimpleEntry<>(u, -v));
    });
    List<String> txns = new ArrayList<>();
    while (!cred.isEmpty() && !debt.isEmpty()) {
        var c = cred.poll(); var d = debt.poll();
        long x = Math.min(c.getValue(), d.getValue());
        txns.add(d.getKey() + " pays " + c.getKey() + ": " + String.format("%.2f", x / 100.0));
        if (c.getValue() > x) cred.add(new AbstractMap.SimpleEntry<>(c.getKey(), c.getValue() - x));
        if (d.getValue() > x) debt.add(new AbstractMap.SimpleEntry<>(d.getKey(), d.getValue() - x));
    }
    return txns;
}

This greedy approach produces at most n − 1 transactions. Finding the true minimum is NP-hard in general, and saying so earns credit.

Machine coding round coming up?

InterviewPilot's machine-coding round focus keeps answers in this shape (requirements, entities, extension points, runnable code) for the whole session, in an overlay that Zoom, Meet and Teams shares don't capture.

Try it free3 free sessions a day · Windows 10/11 · no card required

Other follow-up questions to expect

  • Groups: add a Group holding members and its own BalanceSheet; expenses belong to a group.
  • Split by shares: a new ShareSplit strategy. Show how it plugs in without other changes.
  • Concurrency: two expenses updating the same balance. Synchronise record, or use per-pair locks.
  • Persistence: put repositories behind interfaces; the service doesn't change.
  • Expense history and edits: store expenses; editing means reversing the old splits, then applying the new ones.

What interviewers mark you down for

  • Floating-point money.
  • Split logic inside ExpenseService behind if/else on the type.
  • No validation that shares match the total.
  • No runnable demo.

Frequently asked questions

It's one of the most-reported machine coding problems at Indian product companies and startups, including Flipkart, PhonePe and Swiggy-style loops, and it's a common take-home too.

Usually not. In-memory repositories behind an interface are expected. Mention that you could swap in a database later without changing services.

Work in the smallest unit (paise or cents) as integers. Divide, then give the remainder one unit at a time to the first participants so the shares add up exactly.

It's usually a follow-up or bonus. Get the core expense and balance flow working first.

Was this article useful?

Discussion

No comments yet. Been through an interview like this? Your experience could help the next candidate.

You’re posting anonymously — it appears after a quick review. Sign in to post under your name instantly.
0/2000

Bring backup into your next round.

Install InterviewPilot, upload your resume, and walk in with structured answers a keystroke away — visible to you and nobody else.