This was the phone screen. It was an OOP coding problem about a bank ledger. I had seen someone mention it on the forum but with no specifics, and if you haven't prepared in advance, time gets a bit tight.
The first part is designing a simple dataclass and 2 basic functions, which are just simple addition and subtraction.
The second part is the real focus. You design 2 functions that handle the 2 event types, PAYMENT and FEE. The input is a series of events, and the order may be reversed. If a later-arriving event actually belongs earlier, you have to put it in the correct position and update the add/subtract results caused by the events after it. The idea isn't hard. The key is keeping the event order updated properly and recomputing the balances.
Here is the problem statement, pasted briefly:
Step 1 - Event Handlers
The primary function of loan accounting is to track the state of a loan over time. In this simplified version, we are only tracking two fields:
- Principal Balance - the outstanding principal balance that must be paid back
- Fee Balance - the total fee balance on the loan
There are two possible events that can occur:
- A fee event, which increments the fee balance
- A payment event, which first decrements the fee balance if possible, and then decrements the loan balance with any remaining amount
from dataclasses import dataclass
@dataclass
class Balances:
principal_balance: int
fee_balance: int
def process_payment(balances: Balances, amount: int) -> Balances:
# Implement payment processing logic
raise NotImplementedError()
def process_fee(balances: Balances, amount: int) -> Balances:
# Implement fee processing logic
raise NotImplementedError()
Example
balances = Balances(100000, 0)
after_fee = process_fee(balances, 100)
print(after_fee)
# balance: 100000, fee: 100
Step 2 - Event Processing
Build a method that processes an event and updates the loan state accordingly. Each event has the format: (event_type, amount, accounting_sequence)
class LoanLedger:
def __init__(self):
self.balances = Balances(principal_balance=100000, fee_balance=0)
def process_event(self, event_type, amount, accounting_sequence) -> list[LedgerEntry]:
...
- event_type: "PAYMENT" or "FEE"
- amount: numeric value of the payment or fee
- accounting_sequence: integer representing the order in which these events should be represented on our accounting ledger - this is a proxy for the accounting date
Returns: A list of entries that represent the history of the loan with the following properties: accounting_sequence, principal_balance, fee_balance
Important Context
Events may arrive out of order. For example, we might receive a backdated fee after processing several payments. However, since this stream represents events occurring in real time, events must be processed in the order we receive them. We should not have to re-process all events every time an out-of-order event comes in.
Example
loan_ledger = LoanLedger()
print(loan_ledger.process_event(event_type="PAYMENT", amount=1000, accounting_sequence=1))
# [
# (1, 99000, 0)
# ]
print(loan_ledger.process_event(event_type="PAYMENT", amount=500, accounting_sequence=3))
# [
# (1, 99000, 0),
# (3, 98500, 0)
# ]
print(loan_ledger.process_event(event_type="FEE", amount=100, accounting_sequence=2))
# [
# (1, 99000, 0),
# (2, 99000, 100),
# (3, 98600, 0)
# ]
That's roughly it. Even though the problem says you shouldn't re-process all events, because you have to update the follow-up events the time complexity is still O(n). If I'd had a few more minutes I could have polished it better...
Discussion
Loading comments…