Design in-memory payment and friendship system
Company: Robinhood
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
Design and implement an **in-memory payment system** with simple social features. The system processes a sequence of **requests** that can:
1. **Register a user**
2. **Send a friend request** from one user to another
3. **Accept a friend request** (referencing a previous friend request by its request ID)
4. **Transfer money** between two users
### Rules and Requirements
1. **Users and balances**
- Each user has:
- A unique `user_id` (string)
- A `balance` (integer amount, e.g., cents)
- A list or set of **friends** (other user IDs)
2. **Friend requests**
- A friend request is initiated via a request that includes:
- A unique `req_id` for this friend request
- `user1` and `user2` (both must be existing users)
- An **accept-friend** request does *not* repeat the usernames; instead it contains:
- Its own `req_id` (unique for the accept request)
- The `req_id` of the original friend request to accept
- Only when an accept-friend request is processed successfully do `user1` and `user2` become friends.
3. **Transfers**
- Money can **only** be transferred between users who are **already friends**.
- A transfer request specifies:
- A unique `req_id`
- `from_user`
- `to_user`
- `amount` (> 0)
- A transfer must **fail** if:
- Either user does not exist, or
- The users are not friends, or
- `from_user` does not have enough balance.
- On success:
- Subtract `amount` from `from_user.balance`.
- Add `amount` to `to_user.balance`.
4. **Error handling**
- If a request is invalid (e.g., refers to unknown user, refers to a non-existent friend request ID, tries to accept a friend request twice, etc.), the system should indicate **failure** for that request.
- You can define reasonable behavior for repeated or conflicting requests, but document it in comments.
### Interface
Design classes and methods in an **object-oriented** way.
One possible interface (you can adjust names/signatures as long as behavior is clear):
```python
class PaymentSystem:
def register_user(self, user_id: str, initial_balance: int) -> bool:
"""Register a new user with given balance. Return False if user_id already exists."""
def send_friend_request(self, req_id: str, user1: str, user2: str) -> bool:
"""Store a friend request identified by req_id. Return False on invalid input or duplicate req_id."""
def accept_friend_request(self, req_id: str, original_req_id: str) -> bool:
"""Accept the previously stored friend request with ID original_req_id. Return False if original_req_id doesn't exist, is already accepted, or users are invalid."""
def transfer(self, req_id: str, from_user: str, to_user: str, amount: int) -> bool:
"""Transfer amount from from_user to to_user if they are friends and balance is sufficient. Return True on success, False otherwise."""
```
Your implementation should:
- Maintain all necessary internal state in memory (no external database required).
- Enforce the **friendship** and **balance** constraints for transfers.
- Correctly handle the indirection where a friend-accept request refers to an earlier friend-request by `req_id`.
You may ignore concurrency in the basic solution, but you can optionally comment on how you would make operations atomic under multiple threads.
Quick Answer: This question evaluates object-oriented design skills, stateful in-memory data structures, request processing, and correctness constraints such as friend relationships, unique request IDs, and atomic balance transfers.