InterviewLegend

The Power Day Build Round

Alex Chen18 min readUpdated 55 real interview reports
In this lesson
  • Build a bank ledger with create_account, deposit, and transfer that return the right values
  • Get the transfer edge cases right so a bounced transfer leaves every balance untouched
  • Take the follow-ups Capital One stacks live, from ranking top activity to scheduling transfers

The coding round on Capital One's Power Day usually opens with an empty file and a single instruction: build a small bank. Nothing more, at first. You write create_account, get it working, and only then does the interviewer hand you the next piece. Deposits come first, then transfers between accounts, then a ranking query over everything that has moved. Each requirement arrives once the last one runs, so you spend the hour extending a live system rather than cracking one frozen puzzle.

One habit carries the round, and it owes nothing to a clever algorithm. Hold every account's money in one place, and route each change through a method whose job is plain. Then transfer reads and writes the same balances deposit already touched, and the ranking query reads a running total you were keeping all along. Built that way, each new rule costs a few lines. Reach instead for a pile of special cases and the third requirement makes you unpick the first two.

How Capital One frames it

You build it live in a shared editor and run it against your own inputs as you go, in roughly forty-five minutes to an hour. The interviewer withholds the full specification on purpose. They give you account creation, watch it work, then add deposits, then transfers, then a top_activity ranking, and sometimes a scheduled-transfer twist on top. Candidates who compare notes afterward tend to find the same prompt almost word for word, so learn the shape below cold. Preparation pays. What earns the pass is working, readable output at every step. The tightest possible Big-O is not the thing under the microscope.

The Capital One angle
The stacking is the whole interview. A working create_account is table stakes. What the interviewer watches is how your code absorbs the next requirement. Does transfer reuse the balances deposit already built, and does a failed transfer leave them exactly as they were? By the time top_activity arrives, it should read a total you kept along the way rather than scan history. If your first version buries the balance logic inside deposit, say out loud you will lift it into a shared helper once transfer appears, then do it instead of pasting the same lines twice.

Every operation is really two bookkeeping updates: the balance that changed, and the running activity total that decides the ranking. Update both in step on each successful deposit and transfer and top_activity becomes a sort, never a recount.

The structure: two maps and one honest transfer

The whole ledger lives in two dictionaries. One maps each account id to its balance. The other maps the same ids to a running activity total, the sum of every dollar that has moved through the account. create_account seeds both at zero and refuses to clobber an id that already exists. deposit is short: bail out if the account is missing, otherwise raise the balance and bump the matching activity by the same amount.

python
class Bank:
def __init__(self):
self.balances = {} # account_id -> current balance
self.activity = {} # account_id -> total money moved through this account
def deposit(self, account_id, amount):
if account_id not in self.balances:
return None # no account: nothing to deposit into
self.balances[account_id] += amount
self.activity[account_id] += amount # the deposit counts as activity
return self.balances[account_id]

transfer is the one method with real branching. It touches two accounts and can fail three ways, so every guard runs before a single cent moves. The order matters. Validate that both accounts exist, reject a transfer to the same account, and confirm the funds are there. Only then do you move the money, and you record the amount as activity on both the sender and the receiver.

python
def transfer(self, from_id, to_id, amount):
if from_id not in self.balances or to_id not in self.balances:
return False # a missing account on either side
if from_id == to_id:
return False # a self-transfer moves nothing
if self.balances[from_id] < amount:
return False # insufficient funds
self.balances[from_id] -= amount
self.balances[to_id] += amount
self.activity[from_id] += amount
self.activity[to_id] += amount # both sides record the same volume
return True

top_activity does no counting of its own. Because every deposit and transfer already kept activity current, ranking is one sort by activity descending, breaking ties on the account id so the answer comes back the same every run.

Asked at Capital One

Build the ledger end to end: the four operations, the transfer edge cases that a failed move must respect, and the scheduled-transfer follow-up worked below.

1

Banking System: Ledger and Top Activity

MediumSignature problem
Problem
You are building the ledger for a small bank. Implement a Bank class with these operations, each stacked on top of the last:

Bank() creates a bank with no accounts.
create_account(account_id) opens an account with a zero balance. Returns true if the account is new, or false if an account with that id already exists (and leaves the existing balance alone).
deposit(account_id, amount) adds amount to the account and returns the new balance, or null if no such account exists.
transfer(from_id, to_id, amount) moves amount from one account to another and returns true on success. It returns false and moves no money if either account is missing, if from_id and to_id are the same, or if the sender's balance is below amount.
top_activity(n) returns the ids of the n accounts with the most total activity. An account's activity is the total money it moves: each deposit into it, and each transfer it sends or receives, adds that amount. Rank by activity from high to low, break ties by account id in ascending order, and return every account if fewer than n exist.

Input encoding. Each test case is a JSON array of operations. The first is ["Bank", []]; the rest are ["create_account", [id]], ["deposit", [id, amount]], ["transfer", [from, to, amount]], and ["top_activity", [n]]. Every operation records its return value: create_account and transfer record a boolean, deposit records the new balance or null, and top_activity records the list of ids.

Input: Bank(); create_account("a"); deposit("a",100); transfer("a","b",50); create_account("b"); transfer("a","b",50); top_activity(2)

Output: [null, true, 100, false, true, true, ["a", "b"]]

The first transfer fails because "b" does not exist yet, so nothing moves. Once "b" is open the second transfer succeeds, leaving "a" at 50 and "b" at 50. Activity totals are 150 for "a" (100 deposited plus 50 sent) and 50 for "b", so "a" ranks first.

How this uses the pattern

Keep two dictionaries, balances and cumulative activity. Every successful deposit and transfer updates both. top_activity sorts account ids by activity descending, then by id ascending.

Banking System: Ledger and Top Activity

Loading...
Click "Run" to execute your code

Test Cases

Input: [["Bank",[]],["create_account",["a"]],["create_account",["a"]],["create_account",["b"]]]Expected: [null,true,false,true]
Input: [["Bank",[]],["deposit",["ghost",100]],["create_account",["x"]],["deposit",["x",40]],["deposit",["x",10]]]Expected: [null,null,true,40,50]
Input: [["Bank",[]],["create_account",["a"]],["create_account",["b"]],["deposit",["a",30]],["transfer",["a","b",50]],["transfer",["a","ghost",10]]]Expected: [null,true,true,30,false,false]
Input: [["Bank",[]],["create_account",["a"]],["create_account",["b"]],["deposit",["a",100]],["transfer",["a","b",40]],["deposit",["a",5]],["deposit",["b",5]]]Expected: [null,true,true,100,true,65,45]
Input: [["Bank",[]],["create_account",["bob"]],["create_account",["amy"]],["deposit",["bob",50]],["deposit",["amy",50]],["top_activity",[2]]]Expected: [null,true,true,50,50,["amy","bob"]]
Input: [["Bank",[]],["create_account",["a"]],["deposit",["a",100]],["transfer",["a","b",50]],["create_account",["b"]],["transfer",["a","b",50]],["deposit",["a",30]],["top_activity",[2]]]Expected: [null,true,100,false,true,true,80,["a","b"]]
Input: [["Bank",[]],["create_account",["a"]],["deposit",["a",100]],["transfer",["a","a",30]],["deposit",["a",1]]]Expected: [null,true,100,false,101]
The Capital One angle
Capital One stacks these requirements on you live, one after the next, and grades how the earlier code holds up. A pass comes down to two things. A transfer that fails has to leave both balances exactly as they were, whether the failure was a missing account, a self-transfer, or a shortfall, so validate before you move any money. And top_activity should read a running total you kept on every deposit and transfer instead of walking a history of operations. Say that you are tracking activity as you go, before the ranking requirement even lands, and it costs you one sort when it does.
Take it further
Once the ledger works, Capital One often adds time. A schedule_transfer(from_id, to_id, amount, run_at) books a transfer to run later, and a run_due(now) fires every booked transfer whose time has arrived. The catch is that a scheduled transfer is checked when it runs, not when it is booked, so one that was affordable last week can bounce today. Store the pending transfers in a time-ordered heap and route them through the same guarded transfer core the immediate path uses, and the new rule is about a dozen lines. The full build is worked below.

Summary

  • ✓Hold two maps, balances and cumulative activity, and update both on every successful deposit and transfer. Ranking is then a sort, never a recount.
  • ✓A transfer must validate before it moves money: a missing account, a self-transfer, and a shortfall all return false with both balances untouched.
  • ✓Route balance changes through one place so transfer reuses what deposit built and the next requirement stays a few lines.
  • ✓Capital One stacks requirements while you code. Design the seams up front and top_activity, then scheduled transfers, each land cleanly instead of forcing a rewrite.
Had your Capital One interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Capital One interview guide

Know what Capital One asks before you walk in.

5 coding · 2 system design · 30 behavioral questions

Get Premium$49 a year · Cancel anytime
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.