Early Access: 87 spots left.

Claim
AI Coding InterviewAI Coding InterviewBank Ledger Bug Hunt: Five Production Bugs

Problems

Bank Ledger Bug Hunt: Five Production Bugs

Hard·Tagsai-coding-interviewbug-huntbankingproduction-bugsincident-response

Problem Statement

This problem departs from the standard three-phase arc. There is no feature to add and no optimization to do. The codebase is a small bank ledger with five independent production-style bugs planted in it, and the test suite has five scenarios that fail until each bug is fixed. Your job is to find and fix all five. The shape of this exercise mirrors what real on-call engineers do: a service is misbehaving in several ways at once, and you triage by reading the failing tests, scoping the suspect code, forming a hypothesis per failure, fixing the smallest thing, and verifying with the next test run. The AI is most useful here as a fast pair of eyes for scanning files; it is least useful when you let it propose a rewrite of a method without showing you which line was actually wrong. The codebase ```java class Account { String accountId; String holderName; long balance; // cents; long avoids floating-point money boolean frozen; } class Transaction { String accountId; // the account this transaction concerns String type; // "DEPOSIT", "WITHDRAW", "TRANSFER_IN", "TRANSFER_OUT" long cents; String counterpartyAccountId; // for transfers; null otherwise } class Bank { public String createAccount(String holderName); // returns a new accountId public void deposit(String accountId, long cents); public void withdraw(String accountId, long cents); public void transfer(String fromId, String toId, long cents); public long getBalance(String accountId); public List<Transaction> getHistory(String accountId); public void freezeAccount(String accountId); } ``` The behaviour the tests expect - All amounts are integer cents and must be strictly positive. Zero or negative amounts on `deposit`, `withdraw`, or `transfer` throw `IllegalArgumentException`. - `withdraw` requires sufficient funds. Insufficient funds throw `IllegalStateException`. The balance is unchanged on failure. - `transfer` requires both accounts to exist, the source to have sufficient funds, and the source to not be frozen. Failures throw and leave both balances unchanged. - `freezeAccount` flips a flag. While an account is frozen, `withdraw` and source-side `transfer` throw `IllegalStateException`. Deposits and incoming transfers still work, because real banks freeze the outflow side only. - `getHistory(accountId)` returns the transactions that involve this account, in chronological (insertion) order. For a transfer, both the source account and the destination account see one entry each (`TRANSFER_OUT` and `TRANSFER_IN`). - Unknown account ids throw `IllegalArgumentException`. The validator runs five scenarios 1. bug1_transfer_credits_destination: after `A` (balance $100) transfers $40 to `B` (balance $0), `A` has $60 and `B` has $40. The bug is that the destination's balance never gets credited. 2. bug2_overdraft_rejected: `withdraw` on insufficient funds throws `IllegalStateException` and leaves the balance untouched. `transfer` from a source that does not have the funds also throws and leaves both balances untouched. 3. bug3_amounts_must_be_positive: `deposit`, `withdraw`, and `transfer` reject zero and negative amounts with `IllegalArgumentException`. State is unchanged on rejection. 4. bug4_frozen_account_rejects_outflow: a frozen account cannot `withdraw` and cannot be the source of a `transfer`. Both throw `IllegalStateException`. The frozen account can still receive deposits and incoming transfers. 5. bug5_history_is_per_account: `getHistory(accountId)` returns only the transactions that involve this account, in insertion order. The bug is that it returns the bank's entire global history regardless of the account id. The scenarios are independent. Fixing them in any order produces incremental progress on the per-test count.

Examples

Example 1
Input
A.balance = $100, B.balance = $0; bank.transfer(A, B, $40); bank.getBalance(B)
Output
"$40"
Why
Bug 1 currently leaves B at $0. The fix is to credit the destination after debiting the source.
Example 2
Input
A.balance = $50; bank.withdraw(A, $200)
Output
"throws IllegalStateException; A.balance still $50"
Why
Bug 2 currently allows the withdrawal and pushes the balance to -$150. The fix is to validate before mutating.
Example 3
Input
bank.freezeAccount(A); bank.withdraw(A, $10)
Output
"throws IllegalStateException"
Why
Bug 4 currently allows the withdrawal because no operation actually checks the frozen flag.

Constraints

  • All amounts are integer cents (long) and must be strictly positive on deposit, withdraw, and transfer.
  • withdraw and source-side transfer require sufficient funds. Insufficient funds throw IllegalStateException.
  • Frozen accounts cannot withdraw and cannot be the source of a transfer. They can still receive deposits and incoming transfers.
  • getHistory(accountId) returns only this account's transactions, in insertion order. For a transfer, the source sees a TRANSFER_OUT and the destination sees a TRANSFER_IN.
  • Unknown account ids throw IllegalArgumentException.
  • Failed operations (any throw) leave all state unchanged. No partial mutations, no orphan history entries.

Hints

Stuck? Reveal a nudge toward the right pattern, one step at a time.

Hint 1
Triage tip: run the validator first and read every scenario's failure message before opening any source file. The error messages name the suspect operation each time (transfer, withdraw, freeze, getHistory). Match each failure to a method.
Hint 2
Bug 1 hint: open Bank.transfer. The source line debits, the history entries get added, but the line that credits the destination is missing. Add `to.balance += cents;` in the right place.
Hint 3
Bug 2 hint: open Bank.withdraw. There is no balance check. The fix is `if (a.balance < cents) throw new IllegalStateException(...)` before the mutation. Same pattern goes inside transfer for the source account.
Hint 4
Bug 3 hint: deposit, withdraw, and transfer all need a `if (cents <= 0) throw new IllegalArgumentException(...)` guard at the top. Three places, same shape.
Hint 5
Bug 4 hint: open freezeAccount. The flag IS being set. The bug is that no other method reads it. Add `if (a.frozen) throw new IllegalStateException(...)` inside withdraw and inside transfer for the source. Do NOT add it to deposit or to the destination side of transfer; the spec keeps those open.
Hint 6
Bug 5 hint: getHistory currently returns the bank-wide history list. Filter it: keep only entries whose `getAccountId()` equals the requested accountId. A simple stream or a for-loop into a fresh ArrayList both work.
Hint 7
Validate-then-mutate is the discipline the test harness rewards. Run all checks (account exists, amount positive, sufficient funds, not frozen) BEFORE you change any state. A failed call must leave the bank in the same state it entered.
Hint 8
AI usage tip: scope each prompt to one method. 'In Bank.withdraw, what validation is missing?' produces a precise answer. 'Find all the bugs' produces a sweeping rewrite that is hard to verify.