Bird
Raised Fist0
LLDsystem_design~7 mins

User, Group, Expense classes in LLD - System Design Guide

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Problem Statement
When managing shared expenses among multiple people, tracking who owes what and how expenses are split can become confusing and error-prone. Without clear organization, users may miscalculate balances, and groups may lack proper structure to manage shared costs.
Solution
Create clear classes to represent Users, Groups, and Expenses. Users represent individuals, Groups organize users together, and Expenses record costs shared among group members. This structure helps track who paid, who owes, and how much, making expense management clear and maintainable.
Architecture
┌────────┐       ┌────────┐       ┌───────────┐
│  User  │──────▶│  Group │──────▶│  Expense  │
└────────┘       └────────┘       └───────────┘
     ▲               │                ▲
     │               │                │
     └───────────────┴────────────────┘

This diagram shows Users belonging to Groups, and Groups having multiple Expenses associated with them.

Trade-offs
✓ Pros
Clear separation of concerns: Users, Groups, and Expenses have distinct responsibilities.
Easy to extend: New features like expense splitting rules can be added without major changes.
Improves maintainability by organizing data logically.
✗ Cons
Initial setup requires careful design to handle relationships correctly.
May introduce complexity if groups or expenses have many edge cases.
Without proper validation, data inconsistencies can occur (e.g., user not in group but assigned expense).
Use when building applications that manage shared expenses among multiple users, especially when groups and multiple expenses need tracking.
Avoid if the system only involves single users with no grouping or shared expenses, as the added complexity is unnecessary.
Real World Examples
Splitwise
Uses User, Group, and Expense models to track shared bills and balances among friends and roommates.
Venmo
Manages users and groups to facilitate peer-to-peer payments and shared expense tracking.
Airbnb
Tracks users and groups for splitting rental costs and expenses among travelers.
Code Example
The before code mixes all data in dictionaries without clear structure, making it hard to manage. The after code defines User, Group, and Expense classes to organize data clearly. Group ensures only members can pay or participate in expenses, improving data integrity and maintainability.
LLD
### Before: No clear structure, all data mixed

expenses = [
    {"payer": "Alice", "amount": 100, "participants": ["Bob", "Charlie"]},
    {"payer": "Bob", "amount": 50, "participants": ["Alice"]}
]

# No clear user or group representation


### After: Using User, Group, Expense classes

from typing import List

class User:
    def __init__(self, user_id: int, name: str):
        self.user_id = user_id
        self.name = name

class Expense:
    def __init__(self, expense_id: int, amount: float, paid_by: User, participants: List[User]):
        self.expense_id = expense_id
        self.amount = amount
        self.paid_by = paid_by
        self.participants = participants

class Group:
    def __init__(self, group_id: int, name: str, members: List[User]):
        self.group_id = group_id
        self.name = name
        self.members = members
        self.expenses: List[Expense] = []

    def add_expense(self, expense: Expense):
        if expense.paid_by not in self.members:
            raise ValueError("Payer must be a group member")
        for participant in expense.participants:
            if participant not in self.members:
                raise ValueError("All participants must be group members")
        self.expenses.append(expense)

# Example usage
alice = User(1, "Alice")
bob = User(2, "Bob")
charlie = User(3, "Charlie")

friends = Group(1, "Friends", [alice, bob, charlie])

expense1 = Expense(1, 100.0, alice, [bob, charlie])
expense2 = Expense(2, 50.0, bob, [alice])

friends.add_expense(expense1)
friends.add_expense(expense2)
OutputSuccess
Alternatives
Flat Expense List
Stores all expenses in a single list without grouping users or organizing by groups.
Use when: Choose when the application is very simple with no need for grouping or complex user relationships.
Event-based Expense Tracking
Tracks expenses as events with participants instead of fixed groups and users.
Use when: Choose when expenses are tied to specific events rather than ongoing groups.
Summary
Organizing Users, Groups, and Expenses into classes helps manage shared costs clearly and reliably.
This structure supports tracking who paid and who owes within groups, improving data integrity.
Proper class design makes the system easier to extend and maintain as features grow.

Practice

(1/5)
1. Which class is primarily responsible for storing information about individual people in a shared expense system?
easy
A. Payment
B. User
C. Expense
D. Group

Solution

  1. Step 1: Understand the role of each class

    User class represents individual people, Group holds multiple users, Expense tracks costs.
  2. Step 2: Identify which class stores individual info

    User class stores personal details like name and ID for each person.
  3. Final Answer:

    User -> Option B
  4. Quick Check:

    User = Individual person [OK]
Hint: User class = individual person info [OK]
Common Mistakes:
  • Confusing Group with User
  • Thinking Expense stores user details
  • Assuming Payment is a class here
2. Which of the following is the correct way to define a Group class that holds multiple User objects in Python?
easy
A. class Group: def __init__(self): self.users = []
B. class Group: def __init__(self): self.user = {}
C. class Group: def __init__(self): self.expenses = []
D. class Group: def __init__(self): self.members = None

Solution

  1. Step 1: Identify correct attribute for multiple users

    A list is suitable to hold multiple User objects, so self.users = [] is correct.
  2. Step 2: Check other options for correctness

    class Group: def __init__(self): self.user = {} uses a dict named user which is not typical for holding users; class Group: def __init__(self): self.expenses = [] uses expenses which belongs to Expense class; class Group: def __init__(self): self.members = None sets members to None which is not a collection.
  3. Final Answer:

    class Group: def __init__(self): self.users = [] -> Option A
  4. Quick Check:

    Group holds list of users = self.users = [] [OK]
Hint: Group holds list of users with self.users = [] [OK]
Common Mistakes:
  • Using dict instead of list for users
  • Confusing expenses with users
  • Initializing members as None instead of a list
3. Given the following code snippet, what will be the output?
class Expense:
    def __init__(self, amount, paid_by, split_between):
        self.amount = amount
        self.paid_by = paid_by
        self.split_between = split_between

    def split_amount(self):
        return self.amount / len(self.split_between)

expense = Expense(120, 'Alice', ['Alice', 'Bob', 'Charlie'])
print(expense.split_amount())
medium
A. Error
B. 60
C. 120
D. 40.0

Solution

  1. Step 1: Understand the split_amount method

    It divides total amount by number of people in split_between list.
  2. Step 2: Calculate the split

    Amount = 120, split_between has 3 people, so 120 / 3 = 40.0.
  3. Final Answer:

    40.0 -> Option D
  4. Quick Check:

    120 divided by 3 = 40.0 [OK]
Hint: Divide amount by count of split_between list [OK]
Common Mistakes:
  • Forgetting to divide by number of people
  • Dividing by 2 instead of 3
  • Assuming paid_by affects split amount
4. Identify the bug in this Expense class method that calculates each user's share:
class Expense:
    def __init__(self, amount, paid_by, split_between):
        self.amount = amount
        self.paid_by = paid_by
        self.split_between = split_between

    def split_amount(self):
        return self.amount // len(self.split_between)
medium
A. Using integer division (//) may lose cents in split
B. split_between should be a dictionary, not a list
C. paid_by should be a list, not a single user
D. amount should be a string, not a number

Solution

  1. Step 1: Analyze the division operator used

    The method uses integer division (//) which truncates decimals.
  2. Step 2: Understand impact on money split

    Using // can lose fractional cents, causing inaccurate splits.
  3. Final Answer:

    Using integer division (//) may lose cents in split -> Option A
  4. Quick Check:

    Integer division truncates decimals, causing loss [OK]
Hint: Use float division (/) for accurate money splits [OK]
Common Mistakes:
  • Ignoring decimal loss from integer division
  • Confusing data types for paid_by or split_between
  • Thinking amount should be string
5. You want to design a system where multiple users in a group can add expenses, and the system automatically calculates how much each user owes or is owed. Which design approach best supports scalability and clear responsibility?
hard
A. Make User class handle all expense calculations and group management to centralize logic
B. Use only a single Expense class that stores all users and groups inside it to simplify design
C. Create User, Group, and Expense classes where Group manages users and expenses; Expense tracks amount and split; User tracks individual balances updated by Group
D. Store all data in a flat file and calculate splits manually each time without classes

Solution

  1. Step 1: Identify responsibilities for each class

    User holds personal info and balances, Group manages users and expenses, Expense tracks costs and splits.
  2. Step 2: Evaluate design for scalability and clarity

    Create User, Group, and Expense classes where Group manages users and expenses; Expense tracks amount and split; User tracks individual balances updated by Group cleanly separates concerns, making it easier to maintain and scale.
  3. Final Answer:

    Create User, Group, and Expense classes where Group manages users and expenses; Expense tracks amount and split; User tracks individual balances updated by Group -> Option C
  4. Quick Check:

    Clear class roles = scalable design [OK]
Hint: Separate concerns: User, Group, Expense each handle distinct roles [OK]
Common Mistakes:
  • Putting all logic in one class
  • Ignoring separation of concerns
  • Using flat files for complex data