Split methods and math

Splitwise low-level design: classes, splits and balances

10 min read By The EvenOut team

Quick answer

Store money as whole cents, give each split strategy its own validation, and compute balances as paid minus share, per currency. Equal, percent and shares are one weighted function that hands out leftover cents, so $100.00 three ways is 3,334, 3,333 and 3,333 cents. This design matches Splitwise's help center and EvenOut's handling of money, not Splitwise's internals.

Key points

  • Store money as whole cents in a long and hand leftover cents out by largest remainder, so $100.00 three ways is 3,334, 3,333 and 3,333 cents.
  • Equal, percent and shares are one weighted function. Exact amounts and by-item splits only need a check that they add up to the total.
  • A balance is paid minus share, kept per currency, and the balances in one currency always add up to zero.
  • Deleted and pending expenses stay in the table but stop counting. Simplify debts changes who pays whom, never anyone's total.
On this page
  1. What are you designing?
  2. How should you store money?
  3. Which entities do you need?
  4. How do split strategies work?
  5. How do you compute balances?
  6. How does simplify debts fit in?
  7. Which limits should the design carry?
  8. What should you build first?
  9. Frequently asked questions

What are you designing?

A Splitwise-style app records shared expenses, works out who owes whom and records payments. This page is a design that matches what Splitwise’s help center documents as of October 2026. It is not a description of Splitwise’s internals. The same shape works in Java, Python or any other language.

The settle-up search has its own page, the minimum transactions algorithm, and open source projects are listed in bill splitting apps on GitHub. This page covers what surrounds them: money, entities, splits and balances.

How should you store money?

Store every amount as a whole number of cents in a 64-bit integer. Binary floating point cannot hold most decimal fractions exactly. In Java, 0.1 + 0.2 prints 0.30000000000000004, and a split built on numbers like that drifts away from the bill.

Integer cents still leave remainders. $100.00 is 10,000 cents, and 10,000 divided by 3 is 3,333 with 1 cent left over. Someone has to take that cent, so the shares are 3,334, 3,333 and 3,333, which add back to 10,000. Tricount’s FAQ gives the same result for EUR 100, as of October 2026: EUR 33.34 and EUR 33.33. EvenOut shares remainder cents out instead of losing them.

The largest amount you accept decides the integer width. EvenOut takes amounts up to 9,999,999,999.99, which is 999,999,999,999 cents. That fits in a 64-bit long, whose maximum is 9,223,372,036,854,775,807. A 32-bit int stops at 2,147,483,647 cents, or $21,474,836.47, and would overflow on a large property bill.

Which entities do you need?

Five records cover the model, and each has one rule that keeps it consistent.

Entity Holds Rule
User Name, account or guest flag A guest is a person without an account yet. When they join, their shares move to the real user.
Group or friendship Members and their roles An expense belongs to a group or to a friendship, never both.
Expense Description, payer, total in cents, currency, date Currency lives on the expense, so one group can hold several.
Split One person and their share in cents The splits of one expense add up to its total.
Payment Payer, receiver, amount in cents, currency, date Changes balances the same way an expense does.

Store the resulting shares and not only the method. The method is a label for the screen, and the shares are what balances are built from. EvenOut’s Splitwise import shows why. Splitwise’s CSV records only each person’s net change for a row, so EvenOut rebuilds a payer and an exact-amount split that gives the same balances. The original split method is not recovered.

How do split strategies work?

A split strategy turns a total into one share per person. Splitwise lists equal, exact amounts, percentage, shares, adjustment, reimbursement and itemized splits as of October 2026. The sketch below covers the five that EvenOut offers: “Equally”, “Exact amounts”, “Percent”, “Shares” and “By item”.

Each strategy needs its own validation:

  • Equal. Shares differ by at most one cent. People who are left out get no share.
  • Exact amounts. The amounts must add up to the total.
  • Percent. The percents must add up to 100. Store them as basis points (50.00% is 5000) so no floating point is involved.
  • Shares. Every share is above zero. A person with no share is simply not in the split.
  • By item. Each item is split evenly among the people who had it, and the items must add up to the total, so tax and tip go in as items.

Equal, percent and shares are one function. Each is a list of weights: all 1s for equal, the percents, or the share counts. The function gives each person the whole-cent part of the total times their weight, divided by the sum of the weights. It then hands the leftover cents to the people with the largest fractional parts. That is the largest remainder method, and it keeps the sum exact.

Here is one $90.00 dinner for Ana, Ben and Cy run through every strategy.

Strategy Ana Ben Cy
Equal $30.00 $30.00 $30.00
Exact (50, 25, 15) $50.00 $25.00 $15.00
Percent (50, 30, 20) $45.00 $27.00 $18.00
Shares (4, 3, 2) $40.00 $30.00 $20.00
By item $21.67 $21.67 $46.66

The by-item row has three items. Pasta at $30.00 is for Ana and Ben, which is $15.00 each. Steak at $40.00 is Cy’s. A $20.00 dessert is for all three, which is 2,000 cents among 3: 667, 667 and 666. Ana and Ben take the extra cents because they come first in the list. An exact split of 50, 25 and 10 adds up to only $85.00, so it is rejected, and so is a percent split of 50, 30 and 10.

Java sketch

This compiles and runs on Java 17. Split.percent(5000, 3000, 2000).split(9_000) returns 4,500, 2,700 and 1,800 cents, the Percent row of the dinner table, and Split.equal(3).split(10_000) returns 3,334, 3,333 and 3,333.

import java.util.*;

class Split {
    /** A split strategy turns a total in cents into one share per person. */
    interface Strategy { long[] split(long totalCents); }

    /** Equal, percent and shares are one function with different weights. Largest remainder keeps the sum exact. */
    static long[] byWeights(long total, long... w) {
        long sum = 0;
        for (long x : w) {
            if (x <= 0) throw new IllegalArgumentException("weights must be above zero");
            sum += x;
        }
        long[] out = new long[w.length];
        long[] rem = new long[w.length];
        long given = 0;
        for (int i = 0; i < w.length; i++) {
            out[i] = total * w[i] / sum;
            rem[i] = total * w[i] % sum;
            given += out[i];
        }
        Integer[] order = new Integer[w.length];
        for (int i = 0; i < order.length; i++) order[i] = i;
        Arrays.sort(order, (a, b) -> Long.compare(rem[b], rem[a])); // stable: ties keep list order
        for (int k = 0; k < total - given; k++) out[order[k]]++;
        return out;
    }

    static Strategy equal(int people) {
        long[] w = new long[people];
        Arrays.fill(w, 1);
        return total -> byWeights(total, w);
    }

    static Strategy shares(long... s) { return total -> byWeights(total, s); }

    /** Percents are stored as basis points (50.00% is 5000), never as floating point. */
    static Strategy percent(long... basisPoints) {
        if (Arrays.stream(basisPoints).sum() == 10_000) return total -> byWeights(total, basisPoints);
        throw new IllegalArgumentException("percents must add to 100");
    }

    static Strategy exact(long... cents) {
        return total -> {
            if (Arrays.stream(cents).sum() == total) return cents.clone();
            throw new IllegalArgumentException("amounts must add to the total");
        };
    }

    record Item(long cents, int[] people) {}

    static long[] byItem(long total, int people, List<Item> items) {
        long[] out = new long[people];
        long sum = 0;
        for (Item it : items) {
            long[] w = new long[it.people().length];
            Arrays.fill(w, 1);
            long[] part = byWeights(it.cents(), w);
            for (int k = 0; k < part.length; k++) out[it.people()[k]] += part[k];
            sum += it.cents();
        }
        if (sum == total) return out;
        throw new IllegalArgumentException("items must add to the total");
    }

    /** Net balance = paid minus share. The results always add up to zero. */
    static long[] balances(long[] paid, long[] share) {
        long[] net = new long[paid.length];
        for (int i = 0; i < net.length; i++) net[i] = paid[i] - share[i];
        return net;
    }
}

The same idea in pseudo-code

allocate(total, weights):
    sum = total of all weights
    for each person i:
        base[i] = floor(total * weights[i] / sum)
        frac[i] = (total * weights[i]) mod sum
    left = total - total of all base
    give 1 cent each to the `left` people with the largest frac (ties: list order)
    return base

How do you compute balances?

A person’s balance is what they paid minus their share, kept per currency. Say Ana paid the whole $90.00 dinner. Her share is $30.00, so she is at +$60.00, and Ben and Cy are each at -$30.00. The balances add up to zero, which is the check to run after every change.

A payment moves balances the same way an expense does. If Ben pays Ana $30.00, Ben goes from -$30.00 to $0.00 and Ana from +$60.00 to +$30.00.

Currency belongs in the key. Say Ben also pays for a taxi that costs EUR 60.00, split three ways. In euros Ben is at +EUR 40.00 and Ana and Cy are at -EUR 20.00 each, and the dollar balances do not change. Cy now owes $30.00 and EUR 20.00 as two separate balances. Splitwise’s help center describes the same rule as of October 2026: balances stay separate per currency, changing an expense’s currency “only changes the currency label”, and Simplify Debts “works within each currency separately”. EvenOut keeps balances, settle up and simplify per currency and does not convert.

Deleting should set a flag and not remove the row. A deleted expense stops counting toward balances and can be restored, and EvenOut’s delete message says so: it “stops counting toward balances for everyone involved” and can be restored from the expense page. Pending expenses, such as a deposit or a hold, are excluded the same way until someone confirms the final amount.

How does simplify debts fit in?

Simplify debts reduces every balance to one net number per person, then matches people who owe with people who are owed. It changes who pays whom and never what anyone owes in total. Splitwise’s help center says it “never changes anyone’s total balance” and re-runs when an expense or payment is added, so it belongs in a function that runs after every balance change, once per currency. In EvenOut it is a group setting under “Settling up”, tagged “Recommended” and on by default.

The matching step, the proof that N people need at most N minus 1 payments and the case where greedy matching misses the minimum are in the minimum transactions guide, and how simplify debts works has six IOUs worked through.

The interview version is LeetCode 465, “Optimal Account Balancing”, a Hard problem. Given transactions of the form (from, to, amount), it asks for the minimum number of transactions that settle all debts. Solutions reduce to net balances, drop the zeros and search over pairings of what is left, usually with backtracking. A shipping app can stop at greedy matching, which needs at most N minus 1 payments, while the interview problem asks for the true minimum.

Which limits should the design carry?

Put a bound on every list and every rate, even when ordinary users never reach it. EvenOut’s guards show the kind of numbers to pick: amounts up to 9,999,999,999.99, 200 items per expense, 200 people per group, 30 new expenses or payments a minute and 1,000 a day per account, and 50 open repeats per group or friend. Edits, deletes and repeats do not count toward the creation limits.

These are abuse guards, not plan limits, and a normal group never reaches them. When one is hit, the app says “To keep EvenOut fast for everyone, … Need more? Contact us.” Pick each bound from the work it triggers: a cap on people bounds the balance table that simplify walks, and a rate limit bounds how fast a faulty client can write.

What should you build first?

Build in this order: money as cents and the allocation function, then the entities, then net balances with the zero-sum check, then the strategies with their validation, then payments and soft deletion, and simplify last. Add offline queuing and limits after that.

For the arithmetic behind each split, see how to calculate shared expenses and Splitwise’s split types. If you would rather use an app than build one, EvenOut’s free plan has no daily expense limit, and its Splitwise import is free.

Frequently asked questions

Can I use BigDecimal instead of cents?

Yes, if you fix the scale at 2 and write the remainder rule yourself. BigDecimal adds decimals exactly, but dividing $100.00 by 3 still needs a rounding mode, and rounding each share on its own gives 33.33 three times, which is a cent short of the bill. You need the largest remainder step with either type.

How do you break ties when handing out leftover cents?

Use any rule that gives the same answer every time, such as list order, which is what the sketch does. Which person pays the extra cent is a product decision, and the one thing that matters is that the shares add up to the total. EvenOut shares remainder cents instead of losing them.

Do payments need their own table?

Not for the arithmetic. A payment is an expense where one person paid everything and the other person's share is the whole amount. Splitwise's help center and EvenOut both list payments in the activity feed next to expenses, so whether a payment is its own record or a flagged expense is a storage choice. The entity table keeps a Payment record because apps show and delete payments on their own, and the balance code is the same either way.

Sources

Checked October 6, 2026.

  • Splitwise: What is Simplify Debts?
  • Splitwise: What are the different ways I can split an expense?
  • Splitwise: How can I manage a friendship or group with multiple currencies?
  • Splitwise: How can I restore a deleted expense?
  • Tricount: FAQs (1 cent accuracy)
  • LeetCode: 465. Optimal Account Balancing

Keep reading

Split your next bill with EvenOut.

Free, no ads, no limits on groups or expenses. On Android now.

Get it on Google Play App Store Coming soon