cswork / libraryOpen practice workspace

Articles · Anthropic

Anthropic Banking System Design Interview Question: Requirements, APIs, and Trade-Offs

Practice Anthropic's banking system question with requirements, APIs, ledger design, idempotency, concurrency, scaling, and trade-offs.

A banking system sounds like ordinary CRUD until two withdrawals race, a client retries a transfer, or the service crashes after debiting one account but before crediting another. Suddenly, every vague assumption becomes a money bug.

That is why the candidate-reported Anthropic banking system question is useful preparation. It tests whether you can build a correct stateful system, extend it without breaking earlier behavior, and explain the trade-offs behind each decision. Start with the verified Anthropic Software Engineer questions, then use this guide to rehearse the banking prompt from requirements through production follow-ups.

Important context: Anthropic does not publish a fixed banking question. Recent candidates have reported a banking-system prompt in a CodeSignal Industry Coding Assessment, while Anthropic's official careers page only confirms that technical interviews use shared coding environments and evaluate implementation, debugging, and trade-off reasoning. Your invitation remains the source of truth.

Quick Answer: What Is the Banking System Question Testing?

The strongest interpretation is not “design a global bank in 45 minutes.” Candidate reports more often describe a progressive, project-style implementation: create accounts, process deposits, add transfers or rankings, then extend the same code with delayed operations or account merging.

CodeSignal's current Industry Coding Assessment rules match that shape: one project-based question, four progressive levels, and up to 90 minutes. A strong candidate keeps the first version simple while choosing data structures and interfaces that can survive the next requirement.

SignalWhat the interviewer wants to seeCommon failure
State modelingBalances, transactions, ownership, and status have one clear source of truth.Scattered dictionaries that drift apart.
CorrectnessInvalid operations leave state unchanged and money is never created or lost.Mutating before all checks pass.
ExtensibilityNew levels reuse and refine earlier abstractions.Rewriting the whole solution for each level.
Trade-offsYou explain when an in-memory model is enough and when persistence or coordination is required.Starting with a giant distributed architecture.

Is This a Coding Question or a System Design Question?

Prepare for both, but in the right order. The candidate-reported version is closer to object-oriented implementation under evolving requirements than a traditional architecture interview. You may write working code, run tests, and refactor as each level unlocks.

System design skills still matter because follow-ups naturally expose production concerns. An interviewer can ask what changes when operations arrive concurrently, when data must survive restarts, or when accounts live on different shards. Answer those questions only after you have a correct single-process model.

A good opening question is: “Should I implement the in-memory progressive system first, or would you like a production architecture?” That sentence prevents you from solving the wrong problem at the wrong altitude.

Clarify the Requirements Before Choosing an Architecture

Begin by restating the minimum functional scope. A useful first version supports account creation, deposits, withdrawals, transfers, balance lookup, and transaction history. If the prompt is progressive, later levels may add spending rankings, scheduled effects, transfer acceptance, or account merging.

Then establish the invariants. An account balance cannot become negative unless overdrafts are explicitly allowed. A failed operation makes no partial change. A transfer either debits and credits exactly once or does neither. Across a closed set of accounts, the total money after a transfer must equal the total before it.

Ask about timestamps and ordering. Are operations delivered in strictly increasing timestamp order? Can events arrive late? Does a scheduled transfer reserve funds immediately? If two operations share a time, what is the tie-breaker? These details determine whether a map is enough or whether you need a priority queue, version number, or event log.

Define a Small, Precise API Contract

For an in-memory coding version, methods such as createAccount, deposit, transfer, and getBalance are enough. Validate every argument before mutation, return one documented result type, and keep error behavior consistent across methods.

For a production follow-up, translate those operations into resources and commands:

  • POST /accounts creates an account with an immutable account ID.
  • POST /accounts/{id}/deposits records a deposit command.
  • POST /transfers moves an amount from a source to a destination.
  • GET /accounts/{id}/balance returns the current materialized balance and version.
  • GET /accounts/{id}/transactions?cursor=... pages through immutable history.

Every money-changing request should carry a client-generated Idempotency-Key. Store the key, a fingerprint of the request, and the stable result. A retry with the same key and payload returns the original result; the same key with different parameters is rejected. This gives the API an exactly-once effect within the retention window, not magical exactly-once delivery across a network.

Model the Ledger as the Source of Truth

A beginner solution stores only account_id -> balance. That is acceptable for Level 1, but a production answer needs an auditable record. Model an immutable ledger entry with an ID, account ID, amount, direction, transfer ID, timestamp, and status. Keep a materialized balance for fast reads, but treat the ledger as the record you can reconcile.

For transfers, a double-entry model creates equal and opposite entries: a debit from the source and a credit to the destination. The sum of entries for one transfer is zero. That invariant makes reconciliation and investigation much easier than inferring history from a mutable balance.

You also need an IdempotencyRecord and a Transfer state machine. Useful states include PENDING, COMMITTED, FAILED, EXPIRED, and, only if the product supports it, CANCELED. Do not allow arbitrary transitions; for example, a committed transfer should not silently become canceled.

Build the Correct Single-Process Solution First

In a progressive assessment, optimize for correctness and changeability. Use an account map for constant-time lookup, store outgoing totals separately for ranking, and keep a payment or transfer map for status queries. If deferred operations have execution times, a min-heap lets you settle everything due before processing the next timestamp.

The safest mutation pattern is validate, compute, then commit. For a transfer, verify both accounts, reject self-transfer, confirm sufficient funds, calculate both new balances, and only then update the two records and outgoing total. Every early return occurs before state changes.

Write small helpers around invariants rather than around individual levels. A helper such as settleDueEvents(timestamp) can run before every command. A central applyTransfer keeps validation and mutation together. That structure is easier to extend when the fourth level asks you to merge accounts without losing payment ownership or scheduled effects.

Handle Concurrent Transfers Without Double-Spending

Once requests run concurrently, reading a balance and then writing a new value is unsafe. Two withdrawals can both observe $100 and each approve $80. The final balance may be wrong even though every individual check looked valid.

Inside one relational database, wrap the source debit, destination credit, ledger entries, and balance updates in one transaction. You can lock both account rows in a stable account-ID order to avoid deadlocks, use optimistic versions and retry on conflict, or use serializable isolation and retry serialization failures. PostgreSQL documents that serializable transactions preserve an outcome equivalent to some serial execution, but applications still need retry logic.

The invariant to say aloud is simple: no observer can see a debit without its matching credit, and no two committed withdrawals can spend the same funds. Naming the invariant is stronger than merely naming a database feature.

Design Retries and Idempotency Deliberately

Assume the server commits a transfer but the response is lost. The client cannot know whether retrying will duplicate the payment. Stripe's official API documentation illustrates the standard solution: send an idempotency key, save the first completed result for that key, and replay it for matching retries.

Persist the idempotency record in the same transaction as the ledger mutation. If the record is written separately, a crash can leave money moved without evidence that the request was already processed. Include a request fingerprint so an accidental key reuse with a different amount or destination fails visibly.

Be precise with language. Queues usually offer at-least-once delivery, so consumers may see duplicates. Idempotent handlers turn duplicates into harmless replays. “Exactly once” is meaningful only when you define the boundary: exactly one committed effect for one operation ID inside your system of record.

Scale Only After the Correctness Boundary Is Clear

A single relational database is a strong starting point for a banking service because local transactions make the money boundary understandable. Scale reads with replicas or derived views, partition immutable history, and publish analytics events without moving the ledger's source of truth into an asynchronous queue.

Sharding by account improves write scale but makes cross-shard transfers difficult. You can keep both accounts on one home shard when possible, coordinate a distributed transaction for strong atomicity, or use reservations and a saga with explicit intermediate states. Each choice spends something: latency, availability, operational simplicity, or temporary consistency.

If downstream systems need transfer events, write an outbox record in the same database transaction as the ledger update. A publisher later sends that event, and consumers deduplicate it. AWS's transactional outbox guidance exists specifically to avoid the failure window between a successful database write and an unsuccessful message publication.

Do Not Skip Security, Observability, and Reconciliation

Authenticate the caller, authorize access to each account, encrypt sensitive data, minimize stored personal information, and protect commands with rate limits. Administrative corrections should create compensating ledger entries rather than rewriting history.

Operationally, watch transfer failure rate, duplicate-key rate, serialization retries, stuck PENDING transfers, p95 and p99 latency, and divergence between ledger-derived and materialized balances. Run reconciliation jobs that independently recompute balances from ledger entries and alert before quietly correcting anything.

A strong interview answer also explains the recovery path. If an outbox publisher stops, committed transfers remain correct and events catch up later. If a balance view drifts, rebuild it from the ledger. If a cross-shard workflow stalls, surface its state and either retry or compensate according to a documented policy.

A Worked Follow-Up: The Interviewer Adds Failures

Suppose your first solution uses one map and passes the basic tests. The interviewer then asks, “What if two withdrawals arrive together?” Add per-account synchronization or a database transaction, and acquire locks in deterministic order. Do not bolt a global lock onto the final distributed design unless the expected scale truly permits it.

Next comes: “What if the service crashes after the debit?” In one database, make debit, credit, ledger entries, and idempotency record one atomic transaction. Across stores, introduce a transfer state machine and reservation workflow rather than pretending two writes are atomic.

Finally: “What if the event is published twice?” Make the consumer idempotent using the transfer or event ID. This progression demonstrates exactly what the prompt is designed to reveal: you can preserve invariants as requirements evolve instead of replacing a simple design with fashionable components.

A Seven-Day Preparation Plan

DayFocusWhat to produce
Day 1Requirements and invariantsWrite functional scope, error rules, and three money-safety invariants.
Day 2Progressive implementationComplete accounts, deposits, transfers, rankings, and edge-case tests.
Day 3Deferred stateAdd scheduled effects with deterministic timestamp processing.
Day 4API and ledgerDefine commands, immutable entries, transfer states, and idempotency behavior.
Day 5ConcurrencyExplain one locking strategy and one optimistic or serializable alternative.
Day 6Scale and failureDefend single database, sharding, outbox, and saga trade-offs.
Day 7Timed mockImplement for 60 minutes, then spend 20 minutes answering follow-ups aloud.

Frequently Asked Questions

Is the banking system a confirmed Anthropic interview question?

No. Anthropic does not publish this as a guaranteed question. Recent candidates have reported a banking-system prompt in Anthropic-related CodeSignal assessments, so it is relevant practice, but the exact domain, levels, timing, and role can vary.

Should I prepare for CodeSignal or a whiteboard system design round?

Prepare to implement a progressive in-memory system first, then explain production extensions. CodeSignal officially describes the ICA as one four-level project-based question, while some Anthropic roles may have separate architecture discussions.

Should I start with microservices?

Usually not. Begin with one correctness boundary and one transactional database. Introduce services or shards only when a stated scale, ownership, or availability requirement justifies the extra failure modes.

Do I need a double-entry ledger?

Not for the earliest coding level. For a production design, equal debit and credit entries create a valuable conservation invariant and audit trail. Explain the benefit instead of adding it mechanically.

How do I prevent double-spending?

Validate and update the source balance inside the same atomic transaction as the credit and ledger writes. Use ordered row locks, optimistic versions, or serializable isolation, and retry conflicts safely with an idempotency key.

What language should I use?

Use the language in which you can model state, write tests, and refactor quickly. Familiar standard-library maps, heaps, sorting, and time handling matter more than an exotic framework.

Final Takeaway

The Anthropic banking system prompt is valuable because it rewards disciplined evolution. Build the smallest correct system, state the invariants, preserve backward compatibility, and then extend the design through transactions, retries, concurrency, and distributed failure.

Use the Banking System Simulation as your timed implementation, review the Transactional Banking System Design concept for production trade-offs, and continue with the linked Anthropic questions. That loop turns passive system-design knowledge into the working code and clear reasoning an interview can actually evaluate.

Sources and Further Reading

Research note: This guide was checked on August 24, 2026. Anthropic's process and CodeSignal configuration can vary by role, location, and recruiting cycle; follow the rules in your own invitation.