Banking System Simulation
Company: Anthropic
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's ability to design a stateful, multi-level simulation system that processes a time-ordered stream of operations. It tests object-oriented design, incremental complexity management, and handling of deferred effects like scheduled cashback credits. Such object-modeling questions are commonly used in coding interviews to assess practical application of data structures under evolving requirements.
Banking System Simulation - Level 1: Accounts & Balances
Constraints
- 1 <= len(queries) <= 10^4.
- Every query is a list of strings; query[0] is the operation name and query[1] is an integer timestamp (ms).
- Timestamps are strictly increasing integers in [1, 10^15].
- Amounts are integers in [0, 10^9]. Account ids are arbitrary non-empty strings.
- Return List[str]: one result string per query; use "" for an invalid operation.
Examples
Input: [['CREATE_ACCOUNT', '1', 'alice'], ['CREATE_ACCOUNT', '2', 'bob'], ['CREATE_ACCOUNT', '3', 'alice'], ['DEPOSIT', '4', 'alice', '2000'], ['DEPOSIT', '5', 'bob', '100'], ['DEPOSIT', '6', 'ghost', '50'], ['GET_BALANCE', '7', 'alice'], ['GET_BALANCE', '8', 'ghost']]
Expected Output: ['true', 'true', 'false', '2000', '100', '', '2000', '']
Explanation: Duplicate CREATE returns false; DEPOSIT/GET_BALANCE on a missing account return ''.
Input: [['CREATE_ACCOUNT', '1', 'x'], ['DEPOSIT', '2', 'x', '0'], ['DEPOSIT', '3', 'x', '500'], ['DEPOSIT', '4', 'x', '500'], ['GET_BALANCE', '5', 'x']]
Expected Output: ['true', '0', '500', '1000', '1000']
Explanation: Deposits accumulate; a zero deposit is valid and returns the running balance.
Input: [['GET_BALANCE', '1', 'nobody']]
Expected Output: ['']
Explanation: GET_BALANCE on a never-created account returns the empty string.
Hints
- A single dict mapping account_id -> balance is enough for Level 1.
- CREATE_ACCOUNT must not overwrite an existing account: check membership first and return "false" if present.
- DEPOSIT and GET_BALANCE on a missing account both return the empty string, not "0".
Banking System Simulation - Level 2: Transfers & Top Spenders
Constraints
- 1 <= len(queries) <= 10^4.
- Every query is a list of strings; query[0] is the operation name and query[1] is an integer timestamp (ms).
- Timestamps are strictly increasing integers in [1, 10^15].
- Amounts are integers in [0, 10^9]. Account ids are arbitrary non-empty strings.
- Return List[str]: one result string per query; use "" for an invalid operation.
Examples
Input: [['CREATE_ACCOUNT', '1', 'alice'], ['CREATE_ACCOUNT', '2', 'bob'], ['DEPOSIT', '3', 'alice', '2000'], ['TRANSFER', '4', 'alice', 'bob', '500'], ['TOP_SPENDERS', '5', '2'], ['GET_BALANCE', '6', 'bob']]
Expected Output: ['true', 'true', '2000', '1500', 'alice(500), bob(0)', '500']
Explanation: A transfer returns the source's new balance; TOP_SPENDERS lists every account incl. bob(0).
Input: [['CREATE_ACCOUNT', '1', 'a'], ['CREATE_ACCOUNT', '2', 'b'], ['DEPOSIT', '3', 'a', '100'], ['TRANSFER', '4', 'a', 'a', '10'], ['TRANSFER', '5', 'a', 'ghost', '10'], ['TRANSFER', '6', 'ghost', 'a', '10'], ['TRANSFER', '7', 'a', 'b', '500'], ['TRANSFER', '8', 'a', 'b', '100'], ['GET_BALANCE', '9', 'a'], ['GET_BALANCE', '10', 'b'], ['TOP_SPENDERS', '11', '5']]
Expected Output: ['true', 'true', '100', '', '', '', '', '0', '0', '100', 'a(100), b(0)']
Explanation: Transfer is invalid (returns '') on same id, missing party, or insufficient funds; a valid one debits the source.
Input: [['CREATE_ACCOUNT', '1', 'c'], ['CREATE_ACCOUNT', '2', 'a'], ['CREATE_ACCOUNT', '3', 'b'], ['DEPOSIT', '4', 'a', '1000'], ['DEPOSIT', '5', 'b', '1000'], ['DEPOSIT', '6', 'c', '1000'], ['TRANSFER', '7', 'a', 'c', '300'], ['TRANSFER', '8', 'b', 'c', '300'], ['TOP_SPENDERS', '9', '2'], ['TOP_SPENDERS', '10', '10']]
Expected Output: ['true', 'true', 'true', '1000', '1000', '1000', '700', '700', 'a(300), b(300)', 'a(300), b(300), c(0)']
Explanation: Ties in outgoing total break by account id ascending; n larger than the account count returns all.
Hints
- Store both a balance and a running outgoing total per account; a transfer adds `amount` to the source's outgoing total.
- Validate a transfer fully before mutating: missing account, self-transfer, and insufficient funds must all leave state untouched.
- For TOP_SPENDERS sort with key (-outgoing, account_id) so higher totals come first and ties break by ascending id.
Banking System Simulation - Level 3: Payments & Deferred Cashback
Constraints
- 1 <= len(queries) <= 10^4.
- Every query is a list of strings; query[0] is the operation name and query[1] is an integer timestamp (ms).
- Timestamps are strictly increasing integers in [1, 10^15].
- Amounts are integers in [0, 10^9]. Account ids are arbitrary non-empty strings.
- Return List[str]: one result string per query; use "" for an invalid operation.
Examples
Input: [['CREATE_ACCOUNT', '1', 'alice'], ['CREATE_ACCOUNT', '2', 'bob'], ['DEPOSIT', '3', 'alice', '2000'], ['TRANSFER', '4', 'alice', 'bob', '500'], ['TOP_SPENDERS', '5', '2'], ['PAY', '6', 'alice', '1000'], ['GET_PAYMENT_STATUS', '7', 'alice', 'payment1'], ['GET_PAYMENT_STATUS', '86400007', 'alice', 'payment1'], ['GET_BALANCE', '86400008', 'alice']]
Expected Output: ['true', 'true', '2000', '1500', 'alice(500), bob(0)', 'payment1', 'IN_PROGRESS', 'CASHBACK_RECEIVED', '520']
Explanation: The spec example: 2% cashback (20) on the 1000 payment credits 24h later, lifting alice's balance to 520.
Input: [['CREATE_ACCOUNT', '1', 'a'], ['CREATE_ACCOUNT', '2', 'b'], ['DEPOSIT', '3', 'a', '1000'], ['PAY', '4', 'a', '2000'], ['PAY', '5', 'ghost', '10'], ['PAY', '6', 'a', '1000'], ['GET_PAYMENT_STATUS', '7', 'a', 'payment1'], ['GET_PAYMENT_STATUS', '8', 'b', 'payment1'], ['GET_PAYMENT_STATUS', '9', 'a', 'payment99'], ['GET_PAYMENT_STATUS', '10', 'ghost', 'payment1'], ['TOP_SPENDERS', '11', '2']]
Expected Output: ['true', 'true', '1000', '', '', 'payment1', 'IN_PROGRESS', '', '', '', 'a(1000), b(0)']
Explanation: PAY fails ('') on insufficient funds or missing account; GET_PAYMENT_STATUS is '' for a foreign/unknown payment or missing account; the payment counts toward outgoing.
Input: [['CREATE_ACCOUNT', '1', 'a'], ['DEPOSIT', '2', 'a', '1000'], ['PAY', '3', 'a', '101'], ['GET_BALANCE', '4', 'a'], ['GET_PAYMENT_STATUS', '86400002', 'a', 'payment1'], ['GET_PAYMENT_STATUS', '86400003', 'a', 'payment1'], ['GET_BALANCE', '86400004', 'a']]
Expected Output: ['true', '1000', 'payment1', '899', 'IN_PROGRESS', 'CASHBACK_RECEIVED', '901']
Explanation: Cashback is floor(101*2/100)=2, credited exactly at t+86400000; status flips to received at that instant and the balance rises from 899 to 901.
Hints
- Keep a min-heap of pending cashbacks keyed by credit time (payment_timestamp + 86400000); at the start of every query pop and credit all entries with credit_time <= timestamp.
- Because timestamps are strictly increasing, a single sweep of the heap per query settles everything that is due.
- GET_PAYMENT_STATUS is a pure time comparison: CASHBACK_RECEIVED iff timestamp >= that payment's credit time; also verify the payment id actually belongs to the queried account.
Banking System Simulation - Level 4: Account Merge
Constraints
- 1 <= len(queries) <= 10^4.
- Every query is a list of strings; query[0] is the operation name and query[1] is an integer timestamp (ms).
- Timestamps are strictly increasing integers in [1, 10^15].
- Amounts are integers in [0, 10^9]. Account ids are arbitrary non-empty strings.
- Return List[str]: one result string per query; use "" for an invalid operation.
Examples
Input: [['CREATE_ACCOUNT', '1', 'alice'], ['CREATE_ACCOUNT', '2', 'bob'], ['DEPOSIT', '3', 'alice', '2000'], ['TRANSFER', '4', 'alice', 'bob', '500'], ['TOP_SPENDERS', '5', '2'], ['PAY', '6', 'alice', '1000'], ['GET_PAYMENT_STATUS', '7', 'alice', 'payment1'], ['GET_PAYMENT_STATUS', '86400007', 'alice', 'payment1'], ['GET_BALANCE', '86400008', 'alice']]
Expected Output: ['true', 'true', '2000', '1500', 'alice(500), bob(0)', 'payment1', 'IN_PROGRESS', 'CASHBACK_RECEIVED', '520']
Explanation: Levels 1-3 keep working unchanged when Level 4 is implemented (the spec example).
Input: [['CREATE_ACCOUNT', '1', 'a'], ['CREATE_ACCOUNT', '2', 'b'], ['DEPOSIT', '3', 'a', '100'], ['DEPOSIT', '4', 'b', '200'], ['MERGE_ACCOUNTS', '5', 'a', 'b'], ['GET_BALANCE', '6', 'a'], ['GET_BALANCE', '7', 'b'], ['MERGE_ACCOUNTS', '8', 'a', 'a'], ['MERGE_ACCOUNTS', '9', 'a', 'ghost'], ['MERGE_ACCOUNTS', '10', 'ghost', 'a']]
Expected Output: ['true', 'true', '100', '200', 'true', '300', '', 'false', 'false', 'false']
Explanation: Merge folds b into a (balances summed) and removes b; merge is 'false' on same id or a missing account.
Input: [['CREATE_ACCOUNT', '1', 'a'], ['CREATE_ACCOUNT', '2', 'b'], ['DEPOSIT', '3', 'a', '1000'], ['DEPOSIT', '4', 'b', '1000'], ['TRANSFER', '5', 'a', 'b', '100'], ['PAY', '6', 'b', '500'], ['MERGE_ACCOUNTS', '7', 'a', 'b'], ['GET_PAYMENT_STATUS', '8', 'a', 'payment1'], ['TOP_SPENDERS', '9', '5'], ['GET_PAYMENT_STATUS', '86400006', 'a', 'payment1'], ['GET_BALANCE', '86400007', 'a']]
Expected Output: ['true', 'true', '1000', '1000', '900', 'payment1', 'true', 'IN_PROGRESS', 'a(600)', 'CASHBACK_RECEIVED', '1510']
Explanation: Merge sums outgoing totals (100+500=600) and redirects b's payment and its pending cashback to a, so the status query resolves against a and the 10 cashback later credits a.
Hints
- Track payment ownership in a separate `payment_id -> account_id` map and keep a set of payment ids on each account; a merge rewrites those entries from account 2 to account 1.
- Key the pending-cashback heap by payment id (not account id) and resolve the current owner at settle time — then a merge that redirects ownership automatically redirects the cashback.
- Merge is invalid ("false") on a missing account or a self-merge; on success sum balances and outgoing totals, then delete account 2 so later lookups on it return "".