Implement a simplified multi-level banking system
Company: Circle
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Take-home Project
Quick Answer: Implement a simplified multi-level banking system evaluates algorithm design, data structures, correctness, complexity, edge cases, and implementation details in a realistic interview setting. A strong answer states assumptions, handles edge cases, explains trade-offs, and shows how to validate the result clearly.
Multi-Level Banking System - Level 1: Accounts, Deposits, Transfers
Constraints
- Timestamps are unique, stringified milliseconds, given in strictly increasing order.
- Amounts are non-negative integers.
- All accounts start with a balance of 0.
- A transfer to the same account, with a missing endpoint, or with insufficient funds is rejected (returns '').
Examples
Input: ([['CREATE_ACCOUNT','1','account1'],['CREATE_ACCOUNT','2','account1'],['CREATE_ACCOUNT','3','account2'],['DEPOSIT','4','non-existing','2700'],['DEPOSIT','5','account1','2700'],['TRANSFER','6','account1','account2','2701'],['TRANSFER','7','account1','account2','200']],)
Expected Output: ['true', 'false', 'true', '', '2700', '', '2500']
Explanation: Duplicate create -> 'false'; deposit to missing account -> ''; over-balance transfer (2701 > 2700) -> ''; valid 200 transfer leaves account1 at 2500.
Input: ([['CREATE_ACCOUNT','1','a'],['DEPOSIT','2','a','100'],['TRANSFER','3','a','a','50']],)
Expected Output: ['true', '100', '']
Explanation: Self-transfer (source == target) is rejected.
Input: ([],)
Expected Output: []
Explanation: Empty input -> empty result list.
Input: ([['CREATE_ACCOUNT','1','a'],['CREATE_ACCOUNT','2','b'],['DEPOSIT','3','a','1000'],['TRANSFER','4','a','b','400'],['DEPOSIT','5','b','100']],)
Expected Output: ['true', 'true', '1000', '600', '500']
Explanation: After transfer a=600; b then deposits 100 on top of the received 400 -> 500.
Hints
- Keep a dict mapping accountId -> integer balance.
- CREATE_ACCOUNT must fail (return 'false') if the id already exists.
- TRANSFER returns the SOURCE account's balance, not the target's, and must validate funds and distinct endpoints first.
Multi-Level Banking System - Level 2: Top Spenders Ranking
Constraints
- Outgoing value counts only successful TRANSFER amounts (failed transfers do not count).
- Ties in outgoing value are broken alphabetically by accountId.
- n may exceed the number of accounts; return all of them in that case.
Examples
Input: ([['CREATE_ACCOUNT','1','account1'],['CREATE_ACCOUNT','2','account2'],['CREATE_ACCOUNT','3','account3'],['DEPOSIT','4','account1','10000'],['DEPOSIT','5','account2','10000'],['DEPOSIT','6','account3','10000'],['TRANSFER','7','account1','account2','3000'],['TRANSFER','8','account1','account3','2000'],['TRANSFER','9','account2','account3','4000'],['TOP_SPENDERS','10','3']],)
Expected Output: ['true', 'true', 'true', '10000', '10000', '10000', '7000', '5000', '9000', 'account1(5000), account2(4000), account3(0)']
Explanation: account1 sent 3000+2000=5000, account2 sent 4000, account3 sent 0. The third TRANSFER returns account2's balance (9000).
Input: ([['CREATE_ACCOUNT','1','a'],['CREATE_ACCOUNT','2','b'],['TOP_SPENDERS','3','5']],)
Expected Output: ['true', 'true', 'a(0), b(0)']
Explanation: No spending yet; ties broken alphabetically; n exceeds account count.
Input: ([['TOP_SPENDERS','1','2']],)
Expected Output: ['']
Explanation: No accounts -> empty string.
Input: ([['CREATE_ACCOUNT','1','z'],['CREATE_ACCOUNT','2','y'],['CREATE_ACCOUNT','3','x'],['DEPOSIT','4','z','100'],['DEPOSIT','5','y','100'],['DEPOSIT','6','x','100'],['TRANSFER','7','z','y','50'],['TRANSFER','8','y','x','50'],['TRANSFER','9','x','z','50'],['TOP_SPENDERS','10','2']],)
Expected Output: ['true', 'true', 'true', '100', '100', '100', '50', '100', '100', 'x(50), y(50)']
Explanation: All three spent 50; alphabetical tie-break yields x, y for the top 2.
Hints
- Maintain a separate outgoing-total map updated only on a successful TRANSFER.
- Sort by (-outgoing, accountId) so higher spenders come first and ties are alphabetical.
- Format is exactly 'id(amount)' joined by ', '.
Multi-Level Banking System - Level 3: Scheduled Payments
Constraints
- Payment ids are a global increasing sequence 'payment1', 'payment2', ...
- Due payments are processed before each operation, in scheduling order, and only if the account can cover them.
- A payment exactly at its executeAt timestamp executes when that timestamp is reached.
- CANCEL only succeeds on a still-PENDING payment owned by the account.
Examples
Input: ([['CREATE_ACCOUNT','1','a'],['DEPOSIT','2','a','1000'],['SCHEDULE_PAYMENT','3','a','300','10'],['GET_PAYMENT_STATUS','4','a','payment1'],['DEPOSIT','11','a','0'],['GET_PAYMENT_STATUS','12','a','payment1']],)
Expected Output: ['true', '1000', 'payment1', 'PENDING', '1000', 'PENDING']
Explanation: Payment due at 3+10=13; queries at ts 11 and 12 are before that, so it stays PENDING and the balance is untouched.
Input: ([['CREATE_ACCOUNT','1','a'],['DEPOSIT','2','a','1000'],['SCHEDULE_PAYMENT','3','a','300','10'],['DEPOSIT','20','a','0'],['GET_PAYMENT_STATUS','21','a','payment1']],)
Expected Output: ['true', '1000', 'payment1', '700', 'PAID']
Explanation: By ts=20 (>=13) the payment executes: balance 1000-300=700, status PAID.
Input: ([['CREATE_ACCOUNT','1','a'],['DEPOSIT','2','a','1000'],['SCHEDULE_PAYMENT','3','a','300','100'],['CANCEL_PAYMENT','4','a','payment1'],['GET_PAYMENT_STATUS','5','a','payment1'],['CANCEL_PAYMENT','6','a','payment1']],)
Expected Output: ['true', '1000', 'payment1', 'true', 'CANCELED', 'false']
Explanation: Canceling a PENDING payment succeeds; re-canceling an already-CANCELED payment returns 'false'.
Input: ([['SCHEDULE_PAYMENT','1','nope','100','5'],['GET_PAYMENT_STATUS','2','nope','payment1']],)
Expected Output: ['', '']
Explanation: Scheduling against a missing account returns ''; no payment is created.
Hints
- Process due payments (executeAt <= now) at the start of every operation so balances and outgoing totals stay current.
- Use a global counter for payment ids; store each payment's owner, amount, executeAt, and status.
- A payment that cannot be afforded at execute time still transitions to PAID in this model but debits nothing.
Multi-Level Banking System - Level 4: Merge Accounts
Constraints
- MERGE fails if either account is missing or the two ids are equal.
- Merged balance and outgoing total are the sums of the two accounts'.
- Scheduled payments of the absorbed account are reassigned to the surviving account and execute against its balance.
- After a merge, the absorbed account id is removed and any later operation referencing it behaves as if it does not exist.
Examples
Input: ([['CREATE_ACCOUNT','1','a'],['CREATE_ACCOUNT','2','b'],['DEPOSIT','3','a','1000'],['DEPOSIT','4','b','500'],['TRANSFER','5','a','b','300'],['TRANSFER','6','b','a','100'],['MERGE_ACCOUNTS','7','a','b'],['DEPOSIT','8','a','0'],['TOP_SPENDERS','9','5']],)
Expected Output: ['true', 'true', '1000', '500', '700', '700', 'true', '1500', 'a(400)']
Explanation: Before merge a=700 (sent 300), b=700 (sent 100). Merge: a balance 700+700=1500, outgoing 300+100=400; b removed.
Input: ([['CREATE_ACCOUNT','1','a'],['CREATE_ACCOUNT','2','b'],['DEPOSIT','3','b','1000'],['SCHEDULE_PAYMENT','4','b','400','10'],['MERGE_ACCOUNTS','5','a','b'],['DEPOSIT','20','a','0'],['GET_PAYMENT_STATUS','21','a','payment1']],)
Expected Output: ['true', 'true', '1000', 'payment1', 'true', '600', 'PAID']
Explanation: b's payment (due at 14) is reassigned to a on merge; by ts=20 it executes against a: 1000-400=600, PAID, and is now owned by a.
Input: ([['CREATE_ACCOUNT','1','a'],['MERGE_ACCOUNTS','2','a','a'],['MERGE_ACCOUNTS','3','a','ghost']],)
Expected Output: ['true', 'false', 'false']
Explanation: Merging an account into itself and merging a missing account both return 'false'.
Input: ([['CREATE_ACCOUNT','1','x'],['CREATE_ACCOUNT','2','y'],['DEPOSIT','3','x','100'],['DEPOSIT','4','y','200'],['MERGE_ACCOUNTS','5','x','y'],['GET_PAYMENT_STATUS','6','y','payment1'],['DEPOSIT','7','y','50']],)
Expected Output: ['true', 'true', '100', '200', 'true', '', '']
Explanation: After y is merged into x, any operation referencing y (status check, deposit) returns '' since y no longer exists.
Hints
- Fold accountId2 into accountId1: add balances, add outgoing totals, repoint its pending payments, then delete accountId2.
- Reassign the absorbed account's PENDING payments by changing their owner so future due-processing debits the survivor.
- Guard the no-op and missing-account cases up front and return 'false'.