Build an Account Transfer Ledger
Company: Stripe
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates the ability to implement transaction processing, state management, validation of transfers (including rejects and platform lending), and reasoning about algorithmic time and space efficiency for maintaining account balances and borrowed totals.
Part 1: Compute Final Account Balances
Constraints
- 0 <= len(transactions) <= 200000
- 0 <= timestamp <= 10^9
- 1 <= amount <= 10^9
- Each transaction id is unique
- type is either "deposit" or "transfer"
- For deposits, from_account is an empty string
Examples
Input: [{"id": "t1", "timestamp": 5, "type": "deposit", "from_account": "", "to_account": "A", "amount": 100}, {"id": "t2", "timestamp": 2, "type": "deposit", "from_account": "", "to_account": "B", "amount": 40}, {"id": "t3", "timestamp": 3, "type": "transfer", "from_account": "B", "to_account": "A", "amount": 15}, {"id": "t4", "timestamp": 5, "type": "transfer", "from_account": "A", "to_account": "C", "amount": 30}]
Expected Output: {"A": 85, "B": 25, "C": 30}
Explanation: Process in timestamp order: B gets 40, then B sends 15 to A, then A gets 100, then A sends 30 to C.
Input: [{"id": "t1", "timestamp": 1, "type": "transfer", "from_account": "A", "to_account": "B", "amount": 50}, {"id": "t2", "timestamp": 2, "type": "deposit", "from_account": "", "to_account": "A", "amount": 20}]
Expected Output: {"A": -30, "B": 50}
Explanation: Part 1 does not reject transfers, so A can go negative.
Input: []
Expected Output: {}
Explanation: Edge case: no transactions means no accounts and no balances.
Input: [{"id": "t1", "timestamp": 7, "type": "deposit", "from_account": "", "to_account": "Z", "amount": 7}]
Expected Output: {"Z": 7}
Explanation: Edge case: a single deposit creates one account with that balance.
Hints
- Sort the records by timestamp before processing. Python's sort is stable, so equal timestamps keep their original order.
- Use a hash map to store balances so each transaction update is O(1) on average.
Part 2: Reject Transfers with Insufficient Funds
Constraints
- 0 <= len(transactions) <= 200000
- 0 <= timestamp <= 10^9
- 1 <= amount <= 10^9
- Each transaction id is unique
- type is either "deposit" or "transfer"
- For deposits, from_account is an empty string
Examples
Input: [{"id": "t1", "timestamp": 1, "type": "deposit", "from_account": "", "to_account": "A", "amount": 100}, {"id": "t2", "timestamp": 2, "type": "transfer", "from_account": "A", "to_account": "B", "amount": 70}, {"id": "t3", "timestamp": 3, "type": "transfer", "from_account": "A", "to_account": "C", "amount": 50}, {"id": "t4", "timestamp": 4, "type": "deposit", "from_account": "", "to_account": "C", "amount": 10}]
Expected Output: ({"A": 30, "B": 70, "C": 10}, ["t3"])
Explanation: After A sends 70 to B, A has 30 left. The next transfer of 50 from A is rejected.
Input: [{"id": "t1", "timestamp": 5, "type": "transfer", "from_account": "A", "to_account": "B", "amount": 10}, {"id": "t2", "timestamp": 5, "type": "deposit", "from_account": "", "to_account": "A", "amount": 10}]
Expected Output: ({"A": 10, "B": 0}, ["t1"])
Explanation: Same timestamp: the transfer is processed before the deposit because input order must be preserved.
Input: []
Expected Output: ({}, [])
Explanation: Edge case: no transactions.
Input: [{"id": "d1", "timestamp": 1, "type": "deposit", "from_account": "", "to_account": "X", "amount": 5}, {"id": "tr1", "timestamp": 2, "type": "transfer", "from_account": "X", "to_account": "Y", "amount": 5}]
Expected Output: ({"X": 0, "Y": 5}, [])
Explanation: A valid transfer is applied normally, so there are no rejected ids.
Hints
- The only new logic compared with Part 1 is the conditional check before applying a transfer.
- Even if a transfer is rejected, its accounts should still appear in the output balances if they were mentioned in the input.
Part 3: Auto-Lend Missing Transfer Amounts
Constraints
- 0 <= len(transactions) <= 200000
- 0 <= timestamp <= 10^9
- 1 <= amount <= 10^9
- Each transaction id is unique
- type is either "deposit" or "transfer"
- For deposits, from_account is an empty string
Examples
Input: [{"id": "t1", "timestamp": 1, "type": "deposit", "from_account": "", "to_account": "A", "amount": 50}, {"id": "t2", "timestamp": 2, "type": "transfer", "from_account": "A", "to_account": "B", "amount": 80}, {"id": "t3", "timestamp": 3, "type": "transfer", "from_account": "B", "to_account": "C", "amount": 60}]
Expected Output: ({"A": 0, "B": 20, "C": 60}, {"A": 30, "B": 0, "C": 0})
Explanation: A is short by 30 when sending 80, so the platform lends 30. B later has enough to send 60 to C without borrowing.
Input: [{"id": "t1", "timestamp": 1, "type": "transfer", "from_account": "A", "to_account": "B", "amount": 10}, {"id": "t2", "timestamp": 2, "type": "deposit", "from_account": "", "to_account": "A", "amount": 3}, {"id": "t3", "timestamp": 3, "type": "transfer", "from_account": "A", "to_account": "C", "amount": 5}]
Expected Output: ({"A": 0, "B": 10, "C": 5}, {"A": 12, "B": 0, "C": 0})
Explanation: A borrows 10 for the first transfer, then later borrows 2 more for the second transfer, for a total of 12.
Input: [{"id": "t1", "timestamp": 1, "type": "transfer", "from_account": "A", "to_account": "B", "amount": 5}, {"id": "t2", "timestamp": 1, "type": "transfer", "from_account": "B", "to_account": "C", "amount": 3}]
Expected Output: ({"A": 0, "B": 2, "C": 3}, {"A": 5, "B": 0, "C": 0})
Explanation: Same timestamp: the first transfer is processed first, giving B enough balance to send 3 in the second transfer.
Input: []
Expected Output: ({}, {})
Explanation: Edge case: no transactions means no balances and no borrowing.
Hints
- For a transfer, if balance[from_account] < amount, the exact loan needed is amount - balance[from_account].
- Borrowed totals accumulate over time and are separate from final balances.