In-Memory Banking System: Deposits, Transfers, Top-N Activity, Test Harness
Company: Capital One
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Technical Screen
Implement an in-memory banking system as a class. The interviewer builds it up in three steps, then tests it by feeding in lists of string arrays, one array per operation. You therefore also need a small harness that parses each operation, calls your class and produces the output. In this round the harness can take as long as the class itself, so plan time for it.
### Constraints and Clarifications
- All state is in memory, and calls are single-threaded.
- Account IDs are non-empty strings. Assume amounts are whole numbers in the smallest currency unit, so no floating-point money arithmetic is needed.
- The command names and output format come from the interviewer's tests. The ones used below are illustrative.
### Clarifying Questions
- What should each operation return on success, and on failure?
- Can a deposit or transfer amount be zero or negative?
- May an account transfer money to itself?
### Part 1 — Create accounts and deposit
Implement `create_account(account_id)`, which creates an account with balance 0 and fails if the account already exists, and `deposit(account_id, amount)`, which adds money to an existing account and returns the new balance, and fails if the account does not exist.
```hint Pick the core state
Decide what you keep per account so that the later steps, transfers and an activity ranking, can be added without changing how accounts are stored.
```
#### What This Part Should Cover
- A clear data model for accounts
- Explicit results for duplicate accounts, missing accounts and invalid amounts
- Constant-time operations
### Part 2 — Transfer
Add `transfer(source_id, target_id, amount)`, which moves money between two existing accounts and returns the source account's new balance. It fails if either account is missing or the source does not hold enough money.
```hint Validate before mutating
Make sure a failed transfer leaves both balances exactly as they were.
```
#### What This Part Should Cover
- Every failure case checked before any balance changes
- The self-transfer case
- Extending the Part 1 code without rewriting it
### Part 3 — Top accounts by activity
Add `top_activity(n)`, which returns the `n` accounts with the most activity, most active first.
```hint Keep the ranking cheap
Decide what to update on every deposit and transfer so that the query never replays history, and how the query's cost grows with the number of accounts compared with `n`.
```
#### Clarifying Questions for this Part
- Is activity the number of operations on an account, or the total amount of money moved?
- Do both sides of a transfer count? Do failed operations or account creation count?
- How are ties ordered, and what is returned when `n` is larger than the number of accounts?
#### What This Part Should Cover
- A confirmed definition of activity, maintained incrementally
- A deterministic tie-break, and the case where `n` exceeds the number of accounts
- Query cost relative to the number of accounts and to `n`
### Part 4 — Run the interviewer's tests
Each test is a list of operations, and each operation is an array of strings whose first element names the command. Write a harness that runs a test against a fresh instance of your class and produces one output string per operation, in order, so the outputs can be compared with the expected ones. A test might look like this:
```text
[
["CREATE_ACCOUNT", "acc1"],
["CREATE_ACCOUNT", "acc2"],
["DEPOSIT", "acc1", "500"],
["TRANSFER", "acc1", "acc2", "200"],
["TOP_ACTIVITY", "2"]
]
```
```hint Separate parsing from logic
Keep string handling out of the class. Let the harness convert arguments, dispatch on the command name and format each result, and decide what it does with an unknown command or a malformed number.
```
#### What This Part Should Cover
- Parsing and dispatch that are easy to extend with new commands
- Consistent formatting of results, including failures
- Mismatch reports that name the failing operation
### What a Strong Answer Covers
- Asking what activity means before writing Part 3
- A design that absorbs each new step without rewriting the earlier ones
- Failure handling that never leaves partial state
- A working harness, written early enough to test each step as it is added
- Stated time and space complexity for each operation
### Follow-up Questions
- How would you add a transaction history, so that a balance can be queried as of an earlier point in time?
- If several threads called `transfer` at once, how would you keep balances correct, and avoid deadlock when two transfers involve the same pair of accounts in opposite directions?
- If activity had to cover only a recent sliding time window, what would you store, and how would the ranking change?
Overview: An object-oriented coding exercise that builds an in-memory banking system in stages: creating accounts and deposits, transfers between accounts, and ranking the top N accounts by activity. It also tests writing a harness that runs string-array test commands, and confirming whether activity means operation count or amount moved.
Read the full Capital One Software Engineer interview experience this question came from