Build an Account Transfer Ledger
StripeSoftware Engineer · OnsiteLast updated
Design and implement an in-memory account ledger that supports deposits, transfers, and platform-backed lending with automatic debt repayment.
Problem Statement
You are tasked with building an internal account transfer ledger for a payments platform. The platform allows accounts to hold balances, transfer money to one another, and leverage a shared platform lending facility when an account does not have enough funds to cover an outgoing transfer.
Implement the TransferLedger class with the following methods:
__init__(credit_limit: int): Initializes the ledger with a maximum total credit limit that the platform can lend out across all accounts combined.deposit(account_id: str, amount: int) -> None: Depositsamountintoaccount_id. If the account has any outstanding debt, the deposit must pay down the debt first. Any remaining amount is credited to the account's cash balance.transfer(sender: str, receiver: str, amount: int) -> bool: Attempts to transferamountfromsendertoreceiver.- Returns
Falseifamount =amount`, the transfer proceeds normally. - If
senderhas a cash balance <amount, the platform attempts to lend the shortfall (amount - balance). Iftotal_borrowed + shortfall <= credit_limit, the loan is granted: the sender's cash balance drops to0, their personal debt increases byshortfall, and the platform'stotal_borrowedincreases byshortfall. - If the shortfall exceeds available platform credit, the transfer fails completely, returning
Falsewith no balance or debt changes. - Transferred funds arriving at
receivermust automatically pay down the receiver's outstanding debt first, with any remainder added to the receiver's cash balance. - Returns
Trueif the transfer succeeds.
- Returns
get_balance(account_id: str) -> int: Returns the current cash balance ofaccount_id(always>= 0). Returns0if the account does not exist.get_debt(account_id: str) -> int: Returns the current outstanding debt ofaccount_id. Returns0if the account does not exist.get_total_borrowed() -> int: Returns the total outstanding debt across all accounts currently financed by the platform.
Example
ledger = TransferLedger(credit_limit=100)
ledger.deposit("alice", 40)
assert ledger.get_balance("alice") == 40
# Alice transfers 60 to Bob. Shortfall is 20, which is covered by platform credit.
assert ledger.transfer("alice", "bob", 60) == True
assert ledger.get_balance("alice") == 0
assert ledger.get_debt("alice") == 20
assert ledger.get_balance("bob") == 60
assert ledger.get_total_borrowed() == 20
# Bob transfers 50 to Alice. Alice owes 20 debt, so 20 pays down debt, 30 goes to balance.
assert ledger.transfer("bob", "alice", 50) == True
assert ledger.get_debt("alice") == 0
assert ledger.get_balance("alice") == 30
assert ledger.get_balance("bob") == 10
assert ledger.get_total_borrowed() == 0
# Attempt transfer exceeding remaining platform credit (limit is 100)
assert ledger.transfer("bob", "alice", 200) == False
Constraints
0 <= credit_limit <= 10^91 <= amount <= 10^6for all deposits and transfers- Total operations (calls to any method)
<= 10^5 account_id,sender, andreceiverconsist of lowercase English letters
Hints
Hint 1
Track balance and debt separately for each account, along with a single global variable for total platform debt.
Hint 2
Write a helper function to credit an account, as both deposits and receiving transfers share the exact same repayment-then-balance logic.
Hint 3
Ensure transfers are atomic: validate both input arguments and platform credit capacity before mutating any sender or platform state.
Follow-up questions
- How would you support per-account credit limits alongside the platform-wide credit limit?
- How would you make this ledger thread-safe for high-concurrency environments without acquiring a single global lock for every operation?
- How would you implement an audit log to reconstruct account states at any historical timestamp `t`?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.