DEV Community

Cover image for Building a Simple and Fair Bill Splitting Web App (Without Logins, Databases, or Framework Overkill)
Hashir Khattak
Hashir Khattak

Posted on

Building a Simple and Fair Bill Splitting Web App (Without Logins, Databases, or Framework Overkill)

Splitting bills during group activities seems simple — until it isn’t.
A trip, dinner with friends, shared groceries, or roommate utilities can quickly turn into:

“Wait… who paid for what?”

“Didn’t I cover the last two things?”

“Why am I paying more?”

What we need isn’t a “wallet” app or budgeting suite — just a simple, transparent way to record shared expenses and settle up fairly.

In this post, we’ll walk through how to build a minimal and practical bill-splitting app, similar to the one at:

👉 billsplitonline.com
(live working demo, no login required)

The focus is:
Simplicity, usability, and clarity.

No accounts.
No friction.
No “invite your friends” onboarding.
Just open → add expenses → get settlement results.

Core Problem to Solve

The fair way to split group expenses is:

Track who paid → Track who participated → Calculate who owes who at the end.

We maintain:

people = { Alice, Bob, Charlie }

expenses = [
  { whoPaid: "Alice", amount: 40, sharedBy: ["Alice", "Bob"] },
  { whoPaid: "Bob", amount: 60, sharedBy: ["Bob", "Charlie"] },
  { whoPaid: "Charlie", amount: 30, sharedBy: ["Alice", "Charlie"] }
]

Enter fullscreen mode Exit fullscreen mode

From there we compute:

  • Each person’s total paid
  • Each person’s fair share
  • The net balance (positive = owed money, negative = owes others)

Settlement Algorithm (Minimal Version)

function settleUp(people, expenses) {
  const balance = {};

  // initialize balance
  people.forEach(p => balance[p] = 0);

  // compute costs
  expenses.forEach(e => {
    const splitAmount = e.amount / e.sharedBy.length;
    e.sharedBy.forEach(p => balance[p] -= splitAmount);  // each owes their share
    balance[e.whoPaid] += e.amount;                      // payer gets credit
  });

  // convert to payers and receivers
  const owes = [];
  const owed = [];

  for (const p in balance) {
    if (balance[p] < 0) owes.push({ person: p, amount: -balance[p] });
    if (balance[p] > 0) owed.push({ person: p, amount: balance[p] });
  }

  const settlements = [];

  // match payers to receivers
  while (owes.length && owed.length) {
    const o = owes[0];
    const r = owed[0];
    const amount = Math.min(o.amount, r.amount);

    settlements.push(`${o.person} pays ${r.person} $${amount.toFixed(2)}`);

    o.amount -= amount;
    r.amount -= amount;

    if (o.amount === 0) owes.shift();
    if (r.amount === 0) owed.shift();
  }

  return settlements;
}

Enter fullscreen mode Exit fullscreen mode

This produces clean human-friendly results like:

Bob pays Alice $20.00
Charlie pays Bob $15.00

Enter fullscreen mode Exit fullscreen mode

Live Example of Bill Splitting App

It’s built following the exact principles discussed in this post:

  • No login
  • Works offline
  • Clear settlement output
  • Mobile-first UI
  • Uses the algorithm above

Feel free to inspect the network requests — there aren’t any.
Everything runs client-side.

Final Thoughts

Sometimes we overcomplicate software.
Not every app needs:

  • OAuth
  • Microservices
  • IndexedDB
  • Multi-tenant SaaS architecture

Sometimes the most useful tools are the ones that just work quickly with zero cognitive load.

A small, thoughtful UI + a transparent algorithm solves a real social problem here:
money awkwardness among friends.

Top comments (0)