This is a reconstructed interview-style problem inspired by known bank-account / time-series coding questions from large tech companies. It is not the exact proprietary Airbnb prompt, but it closely matches the intended title, tags (Object-Oriented Design, Data Structures, Time Series), and difficulty.
You are asked to design and implement an in-memory Bank Account System that processes a stream of time-stamped commands.
The system must:
Commands arrive with non-decreasing timestamps. Before processing each new command at time t, the system must automatically execute all scheduled payments whose executeAt time is ≤ t and have not yet been executed or canceled.
Transfers (including scheduled payments) count toward each account’s total outgoing transfer volume, which is used to answer TOP_SPENDERS queries.
The goal is to implement the core processing logic so that, given a list of time-stamped commands, the system returns the outputs for query-type commands in order.
Assume you implement a function with the following abstract signature (adapt to your language of choice):
text processEvents(events: List<List<string>>) -> List<string>
events[i] is a list of tokens (all provided as strings).
Each events[i] has this general form:
text [timestamp, commandName, arg1, arg2, ...]
timestamp is an integer in string form, and the sequence of timestamps is non-decreasing.
The function returns a list of strings, each corresponding to the result of a query-type command, in the same order those queries appear.
All numeric arguments (timestamps, amounts, k) are non-negative integers given as strings. accountId and paymentId are non-empty strings without spaces.
CREATE
text [t, "CREATE", accountId, initialBalance]
accountId does not yet exist, create it with balance = initialBalance.accountId already exists, treat this as an invalid command and ignore it (no output).DEPOSIT
text [t, "DEPOSIT", accountId, amount]
accountId exists, increase its balance by amount.accountId does not exist, ignore the command (no output).TRANSFER
text [t, "TRANSFER", fromId, toId, amount]
fromId and toId exist and fromId has at least amount balance:
fromId’s balance by amount.toId’s balance by amount.fromId’s outgoing transfer total by amount.BALANCE (query)
text [t, "BALANCE", accountId]
accountId exists, append its current balance (as a decimal string) to the output list."ERROR" to the output list.TOP_SPENDERS (query)
text [t, "TOP_SPENDERS", k]
TRANSFER or executed scheduled payments).accountId in lexicographical ascending order.k such accounts (or fewer if fewer exist).accountIds joined by a single comma (,), with no spaces."".SCHEDULE_PAYMENT
text [t, "SCHEDULE_PAYMENT", paymentId, fromId, toId, amount, executeAt]
This creates a future payment with unique identifier paymentId:
executeAt, the system will attempt to transfer amount from fromId to toId.Requirements:
paymentId must be unique among all scheduled payments. If it was already used, ignore this command.fromId or toId does not exist at scheduling time, ignore this command.Scheduled payments are not executed immediately; they are executed automatically when time advances:
t, execute all scheduled payments with executeAt ≤ t that are not already executed or canceled.Execution rules:
fromId has at least amount, perform the transfer and count it toward fromId’s outgoing transfer total.SCHEDULE_PAYMENT itself produces no direct output.
CANCEL_PAYMENT
text [t, "CANCEL_PAYMENT", paymentId]
paymentId exists and has not yet executed or been canceled, mark it as canceled so it will never be executed.paymentId does not exist or has already executed or been canceled, ignore this command.MERGE
text [t, "MERGE", targetId, sourceId]
sourceId into targetId:
sourceId’s balance to targetId’s balance.sourceId:
sourceId is considered deleted and cannot be referenced in future CREATE, DEPOSIT, TRANSFER, BALANCE, or MERGE commands.sourceId:
fromId == sourceId, change fromId to targetId.toId == sourceId, change toId to targetId.fromId and toId are sourceId becomes a payment from targetId to targetId.)accountIds. Closed accounts can still appear in TOP_SPENDERS results if they previously sent transfers.currentTime start at negative infinity (or simply “no time”).t:
currentTime = t.executeAt ≤ currentTime, in deterministic order:
executeAt ascending, then by paymentId lexicographically ascending (or any consistent rule).currentTime.The only commands that append to the output are BALANCE and TOP_SPENDERS.
Input:
text events = [ ["1", "CREATE", "A", "100"], ["2", "CREATE", "B", "50"], ["3", "TRANSFER", "A", "B", "30"], ["4", "BALANCE", "A"], ["5", "BALANCE", "B"], ["6", "TOP_SPENDERS", "2"] ]
Output:
text ["70", "80", "A,B"]
Explanation:
t=1, create account A with balance 100.t=2, create account B with balance 50.t=3, TRANSFER A B 30:
A has enough funds.A balance becomes 70; B balance becomes 80.A’s outgoing transfer total becomes 30.t=4, BALANCE A → append "70".t=5, BALANCE B → append "80".t=6, TOP_SPENDERS 2:
A has sent 30; B has sent 0.[A, B] (only A has positive volume, but B doesn’t count as a spender).[A].k=2, we take up to 2 spenders: only A exists, so the output is "A".If you prefer to include accounts with zero outgoing volume, the problem statement would specify that; in this reconstruction, only accounts with strictly positive outgoing transfer volume are considered spenders.
(The sample output above assumes only positive-volume spenders. A stricter sample could be ["70", "80", "A"].)
For clarity: in this reconstructed spec, the correct final output for this example is
["70", "80", "A"].
Input:
text events = [ ["1", "CREATE", "A", "100"], ["2", "CREATE", "B", "0"], ["3", "CREATE", "C", "50"], ["4", "SCHEDULE_PAYMENT", "p1", "A", "B", "40", "10"], ["5", "SCHEDULE_PAYMENT", "p2", "C", "A", "20", "8"], ["6", "BALANCE", "A"], ["7", "CANCEL_PAYMENT", "p1"], ["8", "BALANCE", "A"], ["9", "MERGE", "A", "C"], ["10", "BALANCE", "A"], ["11", "TOP_SPENDERS", "3"] ]
Step-by-step processing:
t=1: CREATE A 100
A balance = 100.t=2: CREATE B 0
B balance = 0.t=3: CREATE C 50
C balance = 50.t=4: Execute all scheduled payments with executeAt ≤ 4
SCHEDULE_PAYMENT p1 A B 40 10:p1 scheduled from A to B for amount 40 at executeAt=10.t=5: Execute all with executeAt ≤ 5
SCHEDULE_PAYMENT p2 C A 20 8:p2 scheduled from C to A for amount 20 at executeAt=8.t=6: Before processing, execute all with executeAt ≤ 6
p2 is at 8).BALANCE A:A is still 100."100" to output.t=7: Before processing, execute all with executeAt ≤ 7
p2 is at 8, p1 is at 10).CANCEL_PAYMENT p1:p1 is now canceled and will never execute.t=8: Before processing, execute all with executeAt ≤ 8
p2 has executeAt=8 and is pending:
C has balance 50 and A has 100.p2: transfer 20 from C to A.C balance = 30, A balance = 120.C’s outgoing transfer total becomes 20.p1 is canceled and not executed (even though executeAt=10 > 8).
Process BALANCE A:A balance is now 120; append "120".t=9: Before processing, execute all with executeAt ≤ 9
executeAt ≤ 9 (p1 is canceled).
Process MERGE A C:C into A.C balance = 30, so A balance becomes 120 + 30 = 150.C is now closed and cannot be referenced in future commands.C would be rewired to A, but there are none left.t=10: Before processing, execute all with executeAt ≤ 10
p1 is canceled; nothing executes.
Process BALANCE A:A balance is 150; append "150".t=11: Before processing, execute all with executeAt ≤ 11
TOP_SPENDERS 3:p2 (20 from C to A).C: 20A: 0 (no outgoing transfers)B: 0C has positive outgoing volume, even though C is now closed.[C]."C".Output:
text ["100", "120", "150", "C"]
These constraints are a reasonable reconstruction for a medium-difficulty coding problem:
k in TOP_SPENDERS k:
Use hash maps to track accounts, balances, and outgoing transfer totals; maintain scheduled payments in a min-heap keyed by executeAt (and paymentId as a tiebreaker) so you can efficiently execute all due payments before each event, and answer TOP_SPENDERS using a heap or partial sort over the accumulated outgoing transfer totals.