Lessons in this course (8)

8. Design expense sharing (Splitwise) and movie ticket booking

AdvancedMediumLesson 8 of 8

Expense sharing: users, groups, expenses split equally/exactly/by percentage (Strategy), balances between pairs, "simplify debts".

csharp
public interface ISplitStrategy { Dictionary<string, decimal> Split(decimal amount, IReadOnlyList<string> users, IReadOnlyList<decimal>? values); }
public sealed class EqualSplit : ISplitStrategy
{
    public Dictionary<string, decimal> Split(decimal amount, IReadOnlyList<string> users, IReadOnlyList<decimal>? _)
    {
        var share = Math.Round(amount / users.Count, 2);
        var result = users.ToDictionary(u => u, _ => share);
        result[users[0]] += amount - share * users.Count;        // put rounding remainder on the first user
        return result;
    }
}

public sealed class Ledger
{
    private readonly Dictionary<(string From, string To), decimal> _owes = [];
    public void AddExpense(string paidBy, Dictionary<string, decimal> shares)
    {
        foreach (var (user, amount) in shares.Where(s => s.Key != paidBy))
            _owes[(user, paidBy)] = _owes.GetValueOrDefault((user, paidBy)) + amount;
    }
    public decimal NetBalance(string user) =>
        _owes.Where(o => o.Key.To == user).Sum(o => o.Value) - _owes.Where(o => o.Key.From == user).Sum(o => o.Value);
}

Movie ticket booking (BookMyShow): entities Movie, Theatre, Screen, Seat, Show, Booking, Payment. The hard part is seat locking: when a user selects seats, lock them for e.g. 10 minutes; confirm on payment or release on timeout; two users must never book the same seat.

csharp
public interface ISeatLockProvider
{
    bool TryLock(Guid showId, IReadOnlyList<string> seats, string userId, TimeSpan ttl);   // atomic all-or-nothing
    void Unlock(Guid showId, IReadOnlyList<string> seats, string userId);
    bool IsLockedBy(Guid showId, string seat, string userId);
}
// Implementation: in-memory with a lock for one server; Redis SET NX PX per seat or a DB unique constraint on (ShowId, SeatId) for many servers.

Flow: GetAvailableSeats(show) → TryLock(seats) → create Booking (Pending) → payment → Confirm (Booked, unique row per seat) or release on failure/timeout.