PracHub
QuestionsCoachesLearningGuidesInterview Prep

Quick Overview

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.

  • medium
  • Anthropic
  • Coding & Algorithms
  • Software Engineer

Banking System Simulation

Company: Anthropic

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Onsite

# Banking System Simulation You are building an in-memory banking system that is driven by a time-ordered stream of operations. Implement the system so that it processes a list of queries and returns one result per query. Each query is a list of strings. The **first element is the operation name**; the remaining elements are its string arguments. The **second element of every query is a `timestamp`** (an integer number of milliseconds, given in strictly increasing order across the whole input). Amounts are non-negative integers. Account identifiers are arbitrary non-empty strings. Your function receives `queries: List[List[str]]` and must return `List[str]` — the result of each query as a string (use an empty string `""` when an operation is invalid, as defined below). Numbers in the output are rendered without a currency symbol or thousands separators (e.g. `"1500"`). The system is leveled: implement the levels in order. Each higher level adds operations; earlier operations keep working unchanged. ## Level 1 — Accounts and balances - `["CREATE_ACCOUNT", timestamp, account_id]` → `"true"` if a new account is created; `"false"` if an account with that id already exists. - `["DEPOSIT", timestamp, account_id, amount]` → the account's new balance as a string. If the account does not exist, return `""`. - `["GET_BALANCE", timestamp, account_id]` → the account's current balance. If the account does not exist (or no longer exists), return `""`. ## Level 2 — Transfers and spending ranks - `["TRANSFER", timestamp, source_id, target_id, amount]` → the source account's resulting balance. Return `""` if either account is missing, if `source_id == target_id`, or if the source has insufficient funds (no transfer happens in that case). - `["TOP_SPENDERS", timestamp, n]` → the top `n` accounts ranked by **total outgoing amount** (the sum of all `TRANSFER` amounts they sent plus all `PAY` amounts, see Level 3), as a comma-and-space separated string: `"id1(total1), id2(total2)"`. Rank highest total first; break ties by account id in ascending lexicographic order. Include every existing account; if fewer than `n` accounts exist, return all of them. ## Level 3 — Payments with deferred cashback - `["PAY", timestamp, account_id, amount]` → debits `amount` immediately and returns a new payment id of the form `"payment1"`, `"payment2"`, … (a single global counter, incremented on every successful `PAY`). Return `""` if the account is missing or has insufficient funds. Each payment earns **2% cashback** (`floor(amount * 2 / 100)`) that is credited back to the same account **exactly `86400000` ms (24 hours) after** the payment timestamp. - `["GET_PAYMENT_STATUS", timestamp, account_id, payment]` → `"IN_PROGRESS"` if the cashback has not yet been credited at `timestamp`, `"CASHBACK_RECEIVED"` if it has. Return `""` if the account does not exist or the payment id does not belong to that account. Cashback that is due must be settled (added to the balance) **before** evaluating any operation whose timestamp is at or after the cashback time. In other words, every operation should first apply all pending cashbacks whose credit time is `<= timestamp`. ## Level 4 — Account merge - `["MERGE_ACCOUNTS", timestamp, account_id_1, account_id_2]` → `"true"` on success, `"false"` if either account is missing or `account_id_1 == account_id_2`. Merging folds `account_id_2` into `account_id_1`: balances are summed, outgoing-total histories are summed, and any of account 2's pending cashbacks are redirected to account 1. After the merge, `account_id_2` no longer exists, but a `GET_PAYMENT_STATUS` query for a payment originally made by account 2 must still succeed when issued against `account_id_1`. ## Constraints - `1 <= len(queries) <= 10^4`. - Timestamps are strictly increasing integers in `[1, 10^15]`. - Amounts are integers in `[0, 10^9]`. - All outputs are strings. ## Example 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"] ] ``` Output: ``` ["true", "true", "2000", "1500", "alice(500), bob(0)", "payment1", "IN_PROGRESS", "CASHBACK_RECEIVED", "520"] ``` Explanation of the last two lines: alice paid 1000 at t=6, leaving 500. At t=86400006 (24h later) the 2% cashback of 20 is credited, so by t=86400008 her balance is `500 + 20 = 520`.

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

You are building an in-memory banking system driven by a time-ordered stream of operations. Process `queries: List[List[str]]` and return `List[str]` — one result per query. The first element of each query is the operation name and the second is an integer `timestamp` (ms, strictly increasing). Numbers are rendered as plain strings (no currency symbol, no separators). ## Level 1 — Accounts and balances - `["CREATE_ACCOUNT", timestamp, account_id]` → `"true"` if a new account is created; `"false"` if that id already exists. - `["DEPOSIT", timestamp, account_id, amount]` → the account's new balance. Return `""` if the account does not exist. - `["GET_BALANCE", timestamp, account_id]` → the account's current balance, or `""` if it does not exist.

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

  1. A single dict mapping account_id -> balance is enough for Level 1.
  2. CREATE_ACCOUNT must not overwrite an existing account: check membership first and return "false" if present.
  3. DEPOSIT and GET_BALANCE on a missing account both return the empty string, not "0".

Banking System Simulation - Level 2: Transfers & Top Spenders

Extends Level 1 (CREATE_ACCOUNT / DEPOSIT / GET_BALANCE keep working unchanged). Track each account's total **outgoing** amount — the sum of every TRANSFER it sends (plus every PAY at Level 3). ## Level 2 — Transfers and spending ranks - `["TRANSFER", timestamp, source_id, target_id, amount]` → the source's resulting balance. Return `""` (and make no change) if either account is missing, if `source_id == target_id`, or if the source has insufficient funds. - `["TOP_SPENDERS", timestamp, n]` → the top `n` accounts by total outgoing amount as `"id1(total1), id2(total2)"`. Rank by highest total first, breaking ties by account id in ascending lexicographic order. Include every existing account (even those with 0 outgoing); if fewer than `n` exist, return all of them.

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

  1. Store both a balance and a running outgoing total per account; a transfer adds `amount` to the source's outgoing total.
  2. Validate a transfer fully before mutating: missing account, self-transfer, and insufficient funds must all leave state untouched.
  3. 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

Extends Levels 1–2. Adds payments that earn cashback credited 24h later. Before evaluating any query, first apply all pending cashbacks whose credit time is `<= timestamp`. ## Level 3 — Payments with deferred cashback - `["PAY", timestamp, account_id, amount]` → debits `amount` immediately and returns a new payment id `"payment1"`, `"payment2"`, … from a single global counter (incremented on every successful PAY). Return `""` if the account is missing or has insufficient funds. A PAY also counts toward the account's outgoing total. Each payment earns **2% cashback** = `floor(amount * 2 / 100)`, credited back to the same account exactly `86400000` ms after the payment. - `["GET_PAYMENT_STATUS", timestamp, account_id, payment]` → `"IN_PROGRESS"` if the cashback is not yet credited at `timestamp`, else `"CASHBACK_RECEIVED"`. Return `""` if the account does not exist or the payment id does not belong to it.

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

  1. 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.
  2. Because timestamps are strictly increasing, a single sweep of the heap per query settles everything that is due.
  3. 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

Extends Levels 1–3 with account merging. ## Level 4 — Account merge - `["MERGE_ACCOUNTS", timestamp, account_id_1, account_id_2]` → `"true"` on success; `"false"` if either account is missing or `account_id_1 == account_id_2`. Merging folds `account_id_2` into `account_id_1`: balances are summed, outgoing-total histories are summed, and any of account 2's pending cashbacks are redirected to account 1. After the merge `account_id_2` no longer exists, but a `GET_PAYMENT_STATUS` for a payment originally made by account 2 must still succeed when issued against `account_id_1`. All earlier operations (CREATE_ACCOUNT, DEPOSIT, GET_BALANCE, TRANSFER, TOP_SPENDERS, PAY, GET_PAYMENT_STATUS) keep working unchanged.

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

  1. 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.
  2. 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.
  3. 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 "".
Last updated: Jul 1, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,000+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • AI Coding Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • LRU Cache - Anthropic (medium)
  • Account Balance with Expiring Grants - Anthropic (medium)
  • Path Resolution with Symbolic Links - Anthropic (medium)
  • In-Memory Key-Value Database with Nested Transactions - Anthropic (medium)
  • Time-Based Key-Value Store - Anthropic (medium)