One trip, ten problems
Imagine a group of friends (Asha, Ben, Chen and company) on a weekend trip to a hotel. Over two days they split bills, ride the hotel lift, play a game in the lobby, write a trip diary, and then get through a long list of small chores. Every one of those moments is a classic machine-coding problem. This post follows that one trip from the first paragraph to the last, and all the code is modern Java (17 or newer: records, sealed types, enums, and switch expressions).
This second set moves from single-structure problems (Part 1) to problems with multiple actors and rules. Four are worked in full with code that was compiled and run. Six more are class sketches with the design decisions and follow-ups an interviewer will push on, since in a real round you would build one of them, not all.
| Trip scene |
Software problem |
Key idea |
| Friends share the bills and settle up |
Splitwise |
Integer money, split strategies, net balances, greedy settling |
| The hotel lift |
Elevator system |
LOOK/SCAN scheduling, state enum, cost-based dispatch |
| Noughts and crosses in the lobby |
Tic-tac-toe, Snake and Ladders |
Counters for O(1) win checks, rules as data |
| The trip diary |
Logging framework |
Levels, handlers, formatters, thread safety |
| Cinema, reminders, library corner, cab, food, chess |
Six design sketches |
Locking, strategies, state machines, snapshots |
Where the analogy stops being accurate: friends settle up once and trust each other, but software must handle two phones editing the same bill at once. A hotel lift is one shaft you can see, but a real elevator controller must cope with hardware faults. A paper diary has one writer at a time, while a program has many threads. Each section says where the story and the software part ways.
1. Splitwise: the friends share the trip bills
The trip scene (ELI5). Asha pays for dinner, Ben pays for the taxi, and Chen pays for nothing yet. Nobody wants to pull out a calculator at every meal. So they write each bill in a notebook with who paid and who shared it. At the end of the trip, instead of everyone paying everyone, they work out who is up and who is down overall, and the people who are down pay the people who are up. The goal is the fewest handovers of cash.
Requirements: users add expenses paid by one person and split among several (equally, by exact amounts, or by percentage); show who owes whom; simplify debts.
Design decisions:
- Store money as integer paise (a
long), never double. Floating point cannot represent 0.1 exactly: 0.1 + 0.2 prints 0.30000000000000004. Money that drifts by a hair is a bug. BigDecimal is the other correct choice, especially when you need fractions of a paisa, rates, or many currencies; see the BigDecimal Javadoc. Integer paise is simpler and fast when you only add and subtract.
- Splitting 10000 paise three ways gives 3334, 3333, 3333: the leftover paise go to someone, and the shares must sum to the bill.
- A split rule per expense (Strategy pattern): equal, exact, percent. Here they are records implementing one interface. The exact split is the same shape and is left out for space.
- Keep a balance per friend: positive means they are owed, negative means they owe. Net balances are enough to answer "who owes whom" after simplification.
- Simplify by repeatedly matching the biggest creditor with the biggest debtor (greedy). This gives few payments, but finding the true minimum is NP-hard (it behaves like subset-sum partitioning), so say so.
// Java 17
import java.util.*;
record Payment(String from, String to, long paise) {}
interface SplitRule {
Map<String, Long> shares(long totalPaise);
record Equal(List<String> friends) implements SplitRule {
public Map<String, Long> shares(long total) {
long base = total / friends.size();
long extra = total % friends.size(); // leftover paise
Map<String, Long> out = new LinkedHashMap<>();
for (int i = 0; i < friends.size(); i++) {
out.put(friends.get(i), base + (i < extra ? 1 : 0));
}
return out;
}
}
record Percent(Map<String, Integer> pct) implements SplitRule {
public Map<String, Long> shares(long total) {
if (pct.values().stream().mapToInt(Integer::intValue).sum() != 100)
throw new IllegalArgumentException("percentages must add up to 100");
Map<String, Long> out = new LinkedHashMap<>();
long given = 0;
for (var e : pct.entrySet()) {
long s = total * e.getValue() / 100; // rounds down
out.put(e.getKey(), s);
given += s;
}
long left = total - given; // hand leftover paise out one by one
for (String f : out.keySet()) {
if (left-- <= 0) break;
out.merge(f, 1L, Long::sum);
}
return out;
}
}
}
You've reached the free preview of this post.
Subscribe to the Blogs plan (₹99/mo) — or use any Algomi Suite, Career, Investor, or Trader plan — to keep reading.
See plans