Restaurant bill splitting looks like a division problem, but a useful implementation has to model several decisions: personal items, shared dishes, tax, gratuity, discounts, and the final rounding adjustment. If these rules are hidden inside one formula, users cannot tell why their share changed.

This article develops a small, auditable approach that works for both equal and itemized splits.

Define the calculation contract

Before writing code, decide what the inputs mean. A practical data model should distinguish:

  • personal items assigned to one diner
  • shared items assigned to selected diners
  • percentage or fixed tax
  • an included service charge
  • an optional additional tip
  • item-level and order-level discounts

The contract should also state whether percentage charges are calculated before or after discounts. The restaurant receipt is the source of truth.

Build food shares first

Allocate personal and shared food before touching tax or tip. Suppose a shared appetizer costs $18 and diners A, B, and C ate it. Add $6 to each of their food shares. A fourth diner who did not participate receives no part of that charge.

This step should reconcile exactly to the eligible food subtotal. If it does not, the assignment data is incomplete.

Allocate common charges proportionally

Equal allocation is easy, but proportional allocation is usually fairer when orders differ:

function allocateProportionally(amount, foodShares) { const subtotal = foodShares.reduce((sum, value) => sum + value, 0); if (subtotal <= 0) throw new Error("Food subtotal must be positive"); return foodShares.map(share => amount * share / subtotal); } 

A diner responsible for 30% of the food subtotal receives 30% of the allocated tax, service fee, or tip. Run this function only against the diners and items eligible for that charge.

Keep full precision until the end

Rounding each intermediate line can produce a final total that differs from the receipt. Calculate with full precision, then convert final shares to integer cents.

function reconcileCents(rawShares, targetTotal) { const targetCents = Math.round(targetTotal * 100); const exact = rawShares.map(value => value * 100); const cents = exact.map(Math.floor); let remainder = targetCents - cents.reduce((sum, value) => sum + value, 0); const order = exact .map((value, index) => ({ index, fraction: value - Math.floor(value) })) .sort((a, b) => b.fraction - a.fraction); for (let i = 0; i < remainder; i += 1) { cents[order[i % order.length].index] += 1; } return cents.map(value => value / 100); } 

This largest-remainder method distributes leftover cents to the shares with the largest fractional parts. Most importantly, the rounded shares add back to the exact receipt total.

Avoid double tipping

A service charge may already include gratuity. The interface should never assume the user wants another tip. Show included charges separately, label any extra tip clearly, and make the calculation basis visible.

Treat discounts explicitly

An item-specific discount belongs to the diner responsible for that item. An order-wide promotion can be distributed proportionally across eligible food shares. A gift card is a payment method, not necessarily a discount: calculate what everyone owes first, then record how the group paid.

Test the edge cases

Useful tests include:

  1. one diner and no tip
  2. equal split with an indivisible one-cent remainder
  3. a shared item eaten by only part of the group
  4. zero-priced or fully discounted items
  5. tax applied to only some items
  6. an included service charge plus no extra tip
  7. a total that must reconcile after rounding

For manual comparison during QA, a separate Restaurant Bill Split Calculator can help validate ordinary examples before you move to edge cases.

Make the result explainable

A good output shows each diner's food share, allocated tax, service charge, tip, discount, rounding adjustment, and final amount. Users should be able to trace every number back to the receipt.

The arithmetic is not the hardest part. The real product work is defining fair rules, applying them consistently, and guaranteeing that the final shares reconcile to the amount the restaurant expects.