Architecture Interview Lab
Practice for the system design and architecture rounds of Capital One's Senior Staff Software Engineer role in Toronto. You learn each idea by using it: explain it back, run a drill, then defend a design out loud against a clock.
What the interviews probably look like
Capital One calls its final round a Power Day: three or four back-to-back interviews of about an hour each (Exponent). Engineering loops usually include coding, system design, a business case and a behavioral interview. Senior and lead candidates may get a second system design round instead of the case (IGotAnOffer).
The design prompts lean heavily toward banking and cards. "Design a banking system" is the most reported question across levels. Others include card authorization, account balances, payment processing, fraud pipelines and mobile banking backends (Interview Query, PracHub).
This role is more architecture-heavy than a normal engineering loop. The posting puts high-level designs (HLDs), architecture decision records (ADRs), Architecture Review Board work, a capability catalog and business partnership at the centre. Expect at least one conversation to test how you run architecture, not only how you draw it.
Ask your recruiter one question early
Public reports for Senior Staff and architect-level loops are thin, and a November 2025 Toronto principal candidate reported a coding screen first (Taro). Ask: "How many design rounds are there, is there an architecture case or presentation, and is there a coding screen?" Then shift your weekly hours to match.
What "Senior Staff" looks like in the room
A senior candidate produces a solid design with some guidance. At staff and above, interviewers look for someone who drives the conversation, finds the hard part quickly, decides, and treats the interviewer as a peer (Hello Interview, IGotAnOffer). In a bank, add one more: regulated-by-default thinking.
| Signal | What it looks like | What sinks it |
|---|---|---|
| Frames the problem | Names the business goal and the riskiest requirement within five minutes. | Draws boxes before knowing what correctness means here. |
| Drives | Sets the agenda, keeps time, checks in every few minutes. | Waits for the interviewer to ask about scale or failure. |
| Decides | "I'd pick A because of requirement P; the risk is R and I'd watch it with M." | A menu of options with no choice. |
| Finds the crux | Moves fast through the routine parts, then goes deep on money correctness, latency or failure. | Ten minutes on table columns. |
| Regulated by default | Audit trail, idempotency, PII handling and recovery targets mentioned without prompting. | Security as an afterthought in the last minute. |
| Right-sized | The simplest design that meets the requirements; complexity is justified by a number. | Kafka, Kubernetes and three caches for 50 requests a second. |
| Operates it | SLOs, observability, safe deployment, rollback, cost per transaction. | Stops at the diagram. |
| Brings people along | Speaks in decision-record form; thinks about team ownership, migration and adoption. | Ignores how the design ships in a 250-person studio. |
How to use this lab
- Learn (25 min). Read one module section. Watch one linked video at 1.25×.
- Explain it back (10 min). Close the page and say the idea out loud, or write five sentences without notes. Where you stall is what you didn't understand.
- Drill (30 min). Do the module's drills before opening the answer keys.
- One full 45-minute mock from the question bank, recorded on video.
- Score it with the rubric in the Practice lab.
- Redo your weakest 10 minutes from scratch.
- Update the question's status so spaced repetition picks it up.
The ratio that matters
Aim for about 60% of your hours speaking, drawing or writing, and 40% reading or watching. Reading a design feels like learning; producing one under time pressure is what the interview measures.
The 10-week plan
Baseline and method
Read the question bank and The 45-minute method. Do "Design a banking system" cold, recorded, before studying anything. Module 1.
Output: baseline recording and rubric score.
Data and consistency
Module 2. Kleppmann's replication and linearizability lectures.
Output: key schemas for three access patterns; written explanation of write skew.
Money correctness
Module 3 and worked solution S2. Mock: "Design a highly reliable account balance system".
Output: one-page ledger design with journal entries.
Async and events
Module 4. Mock: "Design a cross-region event processing platform".
Output: event envelope and topic design.
Reads, traffic and failure basics
Module 5 and the first half of Module 6. Mock: rate limiter or notification service.
Output: latency budget for one end-to-end request.
Resilience and the flagship question
Rest of Module 6 and worked solution S1. Mock: card authorization, twice; the second time without notes.
Output: DR strategy table for five workloads.
Security and Capital One products
Module 7, worked solutions S3 and S4. Mock: "Let customers edit personal info in the app".
Output: STRIDE table for one flow.
AWS and AI systems
Modules 8 and 10. Re-draw Roseline on AWS. Mock: "Design an enterprise agent platform with guardrails".
Output: GCP-to-AWS mapping of your own systems.
Architecture practice
Module 9 and worked solution S5. Write two ADRs and one HLD. Run the ARB review drill.
Output: two ADRs and a 10-minute HLD talk.
Mock loop
Module 11. Four full mocks, ideally with other people. Polish your stories. Rest the day before.
Output: rubric average of 3 or above on every dimension.
Behind schedule? Protect weeks 1, 3, 6 and 9. They cover the most reported question type (money and cards) and the architecture practice this role is hired for.
Question bank
Read the whole list once, then pick three that make you uneasy and attempt one cold before studying. Your baseline tells you which modules to spend the most time on.
How the questions are grouped
Reported questions come from public candidate reports about Capital One; each card names its source. Products are drawn from the Capital One Canada products named in the job posting. Architecture and review questions practise the ADR, HLD and review-board work the posting describes. Your systems are the deep probes you should expect on your own resume.
Open "Crux and must-mentions" only after your attempt. Mark each question Practiced or Solid; the counts appear in the contents rail.
The 45-minute method
You need a route you can run under pressure without thinking about it. This one has five stages. The fourth stage, where you go deep on the hard part, is where the staff-level signal lives, so the first three must be quick.
1 · Frame (0–5 min)
Your goal is to agree on what "correct" means before you design anything. Ask a few sharp questions, state your assumptions as numbers, then name the crux out loud.
- Who and what: who uses it, and which three flows matter most? Say what you're leaving out.
- How big: users, transactions per second at average and peak, data retention.
- How good: latency target, availability target, recovery time (RTO) and data-loss tolerance (RPO).
- What rules: card data (PCI), personal information (PIPEDA), audit retention, data residency.
Banking requirements checklist: say these without being asked
- Correctness: no money lost, duplicated or half-applied.
- Consistency: per-account strong consistency where decisions depend on balance; eventual is fine for views.
- Idempotency: every money-moving request is safe to retry.
- Auditability: who did what, when, and why the system decided what it did; immutable and retained.
- Security and privacy: data classification, encryption, least privilege, tokenized card numbers.
- Resilience: availability target, RTO and RPO per flow, behaviour when a dependency fails.
- Operability and cost: SLOs, observability, safe deployment, cost per transaction.
2 · Shape (5–10 min)
- Entities: four to six nouns with their keys, such as Account (account_id), Authorization (auth_id, network_txn_id) and JournalEntry.
- API: three to five calls. Put the idempotency key in money-moving calls. Derive the caller's identity from the token, never from the request body.
- Numbers: only the estimates that change a decision. For example, 10,000 per second times 3 KB is about 30 MB/s of writes, so one relational primary is borderline and partitioning matters.
POST /v1/transfers Idempotency-Key: 7f3c…
{ "from": "acc_123", "to": "acc_987", "amount": "50.00", "currency": "CAD" }
→ 201 { "transfer_id": "tr_…", "status": "PENDING" }
GET /v1/transfers/{id}
GET /v1/accounts/{id}/transactions?cursor=…&limit=50
3 · Sketch (10–22 min)
Draw the simplest design that handles the functional requirements end to end, then walk one request through it with numbered steps. Don't add caches, queues or regions yet; mention where they might go and move on.
- Lay it out left to right: clients, then the edge, then services, then data.
- Solid arrows are synchronous calls. Dashed arrows are asynchronous events.
- Write what each data store holds and its key next to it, such as "holds · PK = account_id".
- Draw trust boundaries as dashed boxes: the card-data (PCI) zone, the VPC, a partner edge.
4 · Deepen the crux (22–38 min)
Pick two or three deep dives driven by the crux you named in stage 1. For each one, run the same four beats: options → decision → why → the risk you accept and how you'd watch it.
| Question type | Usual crux | Deep dives to offer |
|---|---|---|
| Banking, transfers, balances | Never lose or duplicate money | Ledger model, idempotency, concurrency on one account, unknown outcomes from external rails, reconciliation |
| Card authorization | Correct available credit at low latency | Atomic reserve, latency budget, fraud timeout fallback, multi-region home-region writes, stand-in |
| Fraud, decisioning, pre-approval | Fast, explainable, governed decisions | Feature freshness, model serving budget, fallback rules, decision audit, offline/online skew |
| Events, notifications, pipelines | Effectively-once effects | Partition keys and ordering, dedupe, DLQ and replay, backpressure, schema evolution |
| Migrations and architecture cases | Change without an outage or a compliance gap | Transition states, parallel run and diffing, rollback per slice, governance checkpoints |
5 · Harden and wrap (38–45 min)
- Failure walk: a dependency is slow, a region is down, a duplicate request arrives, a poison message hits the queue. Say what the system does in each case.
- Operations: two or three service-level indicators (one of them a business signal like approval rate), canary deployment and automatic rollback.
- Evolution: what breaks at 10× load, and what you deliberately deferred.
- Decision summary: your three key decisions, one line each, in ADR form.
Phrases that signal staff-level judgment
Deciding
- "Given X, I'd choose A over B because of requirement P. We accept consequence C."
- "I'm deliberately not adding a cache here. Decisions must read the source of truth."
Scoping
- "I'll assume X. Tell me if that's wrong."
- "I'm treating disputes as out of scope; they'd plug in through the ledger's reversal entries."
Checking in
- "Before I go deeper, is this the area you'd most like to explore?"
- "Let me check the design against the requirements we agreed."
Naming risk
- "This becomes a bottleneck around N per second; the next step would be M."
- "I don't know the exact quota. I'd verify it, and the design doesn't depend on it because…"
Numbers to carry in your head
Rough planning figures. Say "roughly" when you use them, and verify quotas for real designs.
| Conversion | Value |
|---|---|
| Seconds in a day | 86,400 ≈ 10⁵ |
| 1 million per day | ≈ 12 / s |
| 100 million per day | ≈ 1,160 / s |
| 1 billion per day | ≈ 11,600 / s |
| Peak-to-average (consumer) | 2–5× |
| Little's Law: in-flight | rate × latency |
| Availability | Down per year | Per 30 days |
|---|---|---|
| 99.9% | 8.8 h | 43.2 min |
| 99.95% | 4.4 h | 21.6 min |
| 99.99% | 52.6 min | 4.3 min |
| 99.999% | 5.3 min | 26 s |
| Latency (orders of magnitude) | Roughly |
|---|---|
| Memory reference | 100 ns |
| SSD random read | ~100 µs |
| Round trip, same zone | < 1 ms |
| Round trip, across zones in a region | 1–2 ms |
| Key-value read (DynamoDB, Redis) | 1–10 ms |
| Cross-continent round trip | 60–150 ms |
| Sizes | Roughly |
|---|---|
| Card transaction record | 0.5–2 KB |
| Event with envelope | 1–3 KB |
| Monthly statement PDF | 100–300 KB |
| DynamoDB item limit | 400 KB |
| DynamoDB per-partition writes | 1,000 WCU/s |
Trade-offs, numbers and SLOs
Every answer at this level is a trade-off defended with a number. This module gives you the arithmetic to do that in your head while you talk.
Why it matters here
Capital One engineers talk publicly about engineering authorization-grade systems for five nines (see the re:Invent 2025 talk in the reading list). An interviewer will expect you to turn "highly available" into a number and show what it costs.
Core ideas
Tail latency, not the average
If one request fans out to 10 services and each has a 1% chance of being slow, about 10% of requests hit at least one slow call (1 − 0.99¹⁰ ≈ 9.6%). Fan-out turns rare slowness into a common experience, so you design and alert on P99, and you cap fan-out on the critical path.
Availability composes
Dependencies in series multiply: three 99.9% services in a chain give about 99.7%. Redundant copies in parallel give 1 − (1 − a)ⁿ, so two independent 99.9% replicas give 99.9999%. The catch is "independent": shared deployments, shared config and shared regions fail together. The useful moves are taking dependencies off the critical path (async, fallback) and making redundancy truly independent.
Little's Law sizes everything
Requests in flight = arrival rate × time in system. At 10,000 per second and 150 ms, you have 1,500 requests in flight. That number sizes thread pools, database connections and Lambda concurrency. It also explains outages: if a dependency slows to 2 s, in-flight requests jump to 20,000 and you run out of everything at once.
SLIs, SLOs and error budgets
An SLI is what you measure (the share of authorizations answered in under 150 ms). An SLO is the target (99.95% per 30 days). The error budget is what's left: 0.05% of a 30-day month is 21.6 minutes. When the budget is spent, you slow releases and fix reliability. This turns reliability arguments into a shared rule that product and engineering both accept.
Estimates that change decisions
Only estimate what moves a decision: peak writes per second (one primary, or partition?), storage growth (hot store, or archive tier?), in-flight concurrency (pool sizes, quotas). Round hard and say so.
Consistency vs availability, honestly
The CAP theorem only bites during a network partition: then you choose between refusing requests and serving possibly stale data. Most of the time the real trade-off is latency vs consistency (PACELC). In a bank, choose consistency for decisions that move money, and availability plus freshness indicators for views.
Explain it back
- Why does fan-out make P99 more important than the average?
- Two replicas at 99.9% each: when is the combined availability 99.9999%, and when is it closer to 99.9%?
- Use Little's Law to explain how one slow dependency can exhaust a service's connection pool.
- Explain an error budget to a product manager in two sentences.
Drills
D1 · Storage estimate for card authorizations
3,000 authorizations per second on average, peak 4×. Each writes a 1 KB decision record and emits a 2 KB event. Kept one year with 3× replication. What are the peak rate, daily data and yearly storage?
Answer key
Peak = 12,000/s. Daily = 3,000 × 86,400 = 259.2 M records × 3 KB ≈ 778 GB/day. Yearly ≈ 284 TB, × 3 replication ≈ 850 TB. Decision: hot store keeps 13 months for disputes and service; older data moves to S3 with lifecycle rules and Object Lock for audit.
D2 · Concurrency at peak
12,000/s at a P99 of 150 ms. How much concurrency do you need, and what AWS default would bite if this ran on Lambda?
Answer key
12,000 × 0.15 = 1,800 in flight, plus headroom (aim for ~3,000). Lambda's default regional concurrency quota is 1,000, so you'd raise it and use reserved concurrency for this function so other workloads can't starve it. At steady high throughput, also compare cost against Fargate.
D3 · Availability of an authorization path
API Gateway 99.99%, authorization service 99.95%, DynamoDB 99.99%, fraud service 99.9%, all in series. What's the path availability, and what's the cheapest change to reach 99.9%+?
Answer key
0.9999 × 0.9995 × 0.9999 × 0.999 ≈ 99.83% (about 15 hours a year). Make fraud a soft dependency (60 ms timeout, rules-only fallback): the chain becomes ≈ 99.93%. Beyond that you need redundancy at the service and data level across regions.
D4 · Error budget policy
Write a three-line error budget policy for an authorization service with a 99.95% monthly SLO.
Answer key
- Budget is 21.6 minutes of SLI failure per 30 days.
- Above 50% spent: only fixes and canaried changes ship; on-call reviews every release.
- Above 100%: feature freeze until the 30-day window recovers; postmortem actions get top priority.
Watch and read
You're done when
Data: storage, replication, partitioning and consistency
Most banking design mistakes are data mistakes: the wrong store, the wrong key, or a concurrency anomaly nobody noticed. Learn to pick stores from access patterns and to name the anomaly before it bites.
Core ideas
Choose the store from access patterns
List the reads and writes first, then pick. Relational (Aurora PostgreSQL) for multi-row transactions, constraints and ad-hoc queries. DynamoDB for known access patterns with predictable latency at any scale, conditional writes and small transactions. OpenSearch for text search. S3 for documents, archives and the data lake. A cache only for data whose staleness you can tolerate.
Replication and replica lag
Leader–follower is the default. With asynchronous followers, a read right after a write can miss it. Fixes: read from the leader after a write (read-your-writes), pass a version or LSN token, or accept staleness and show "as of" times. Multi-leader brings write conflicts; leaderless quorums need R + W > N for overlap.
Partitioning and hot keys
The partition key decides your scaling and your hot spots. Hash keys spread load; range keys support ordered scans. In a bank the natural key is account_id, but corporate accounts with thousands of cards sharing one limit become hot. DynamoDB caps each partition at roughly 1,000 write units and 3,000 read units per second, so a hot account needs a design: split counters, credit tranches across sub-partitions, or a dedicated store.
Isolation anomalies with money
Lost update: two withdrawals read a $100 balance, both subtract $80, both write. Under PostgreSQL's default Read Committed isolation, this read-then-write pattern is exposed. Write skew: two transactions each check a rule across rows ("combined balance stays above zero"), each writes a different row, and the rule breaks. Fixes: atomic conditional updates, SELECT … FOR UPDATE, optimistic version columns, or SERIALIZABLE isolation with retries.
-- Atomic, safe under concurrency: the check and the write are one statement
UPDATE accounts
SET available = available - :amount, version = version + 1
WHERE account_id = :id AND available >= :amount;
-- 0 rows updated → decline (insufficient funds)
# DynamoDB equivalent
UpdateItem Key={pk: "ACCT#123"}
UpdateExpression "SET available = available - :amt"
ConditionExpression "available >= :amt"Consistency from the customer's seat
Linearizable: after the transfer returns, every read sees it. Read-your-writes: you see your change immediately, others may lag. Monotonic reads: you never see time go backwards between refreshes. Eventual: the search index may trail by seconds. Choose per screen, and show freshness where you choose eventual consistency ("Pending", "Updated 10 s ago").
Change data capture feeds everything else
Keep one system of record per entity, and feed read models, search, analytics and caches from its change stream (DynamoDB Streams, or Debezium on PostgreSQL logical replication). This avoids dual writes and gives you replay.
Explain it back
- Explain write skew with two joint-account withdrawals.
- Why is "read from a replica" dangerous for an authorization decision but fine for the transaction history screen?
- What makes a partition key good, and what makes one hot?
- Why feed search from a change stream instead of writing to both stores?
Drills
D1 · Key design for transaction history
Access patterns: (a) latest 50 transactions for an account, (b) all transactions for an account in a date range, (c) one transaction by ID for a dispute. Design a DynamoDB key schema.
Answer key
PK = ACCT#<account_id>, SK = TXN#<ISO timestamp>#<txn_id>. (a) and (b) are Query with descending order and a BETWEEN on SK. (c) uses a GSI with PK = TXN#<txn_id>. Archive data older than the hot window to S3 and query it with Athena for older disputes.
D2 · Spot the bug
bal = SELECT available FROM accounts WHERE id = :id
if bal >= amount:
UPDATE accounts SET available = :bal - :amount WHERE id = :id
INSERT INTO holds (...)What goes wrong under concurrency, and how do you fix it?
Answer key
Lost update: two requests read the same balance and both write. Fix with one conditional update (shown above), in the same transaction as the hold insert, or with SELECT … FOR UPDATE. Also add an idempotency key so retries don't create a second hold.
D3 · The hot corporate account
A corporate account has 4,000 employee cards sharing one $2M limit, with peaks of 2,000 authorizations a second. DynamoDB partitions cap at about 1,000 writes a second. Design it.
Answer key
Split the open-to-buy into N tranches (e.g. 8 sub-items, $250K each), and route each card to a tranche by hash. Each authorization does a conditional decrement on its tranche. A rebalancer moves headroom between tranches asynchronously. When one tranche is empty, try a second tranche before declining. Accept that the account can be declined slightly before the full limit is reached; agree that tolerance with the business. Name the alternative: a single-writer actor per account in memory, which gives exact accounting but needs careful failover.
Watch and read
You're done when
Money correctness: ledgers, idempotency, sagas, reconciliation
This is the crux of most Capital One prompts. If you can show money is never lost, duplicated or half-applied, and that every movement is explainable later, you've passed the hardest part of a banking design.
Core ideas
Double-entry ledger
Every movement is a journal entry whose lines balance: total debits equal total credits. Entries are append-only and immutable; you correct mistakes with a new reversing entry, never an update. A balance is the sum of an account's lines. You store a materialized balance for speed, but it's updated in the same transaction as the entry, so the two can't disagree. Internal accounts (fees, suspense, network settlement) make every flow balance.
| Entry: customer A pays $50 to customer B | Account | Debit | Credit |
|---|---|---|---|
| line 1 | A · deposit account | 50.00 | |
| line 2 | B · deposit account | 50.00 |
For deposit accounts the bank owes the customer, so a debit reduces A's balance and a credit increases B's.
Ledger balance vs available balance
Ledger (posted) balance is what has settled. Available balance subtracts holds and pending items. For a card: available credit = limit − posted balance − open authorizations. Decisions use available; statements use posted. Mixing the two is a classic interview mistake.
Idempotency keys
The client sends a unique key with each money-moving request. The server stores the key, a hash of the request and the result, in the same transaction as the effect. A retry with the same key returns the stored result. The same key with a different body is rejected (HTTP 422). A request still in progress gets a 409 or waits on a lock. Keys expire after a window such as 24 hours.
"Exactly once" means at-least-once delivery plus idempotent effects
Networks can't deliver exactly once. Messages will be duplicated, so every consumer either dedupes on a message ID or makes its effect naturally idempotent (an upsert keyed by transaction ID).
Transactional outbox
Writing to the database and then publishing an event is a dual write: a crash in between loses the event. Instead, write the event to an outbox table in the same transaction as the state change, and let a relay (polling or CDC) publish it. You get at-least-once events that never contradict the database.
Sagas instead of distributed transactions
When a flow spans services or external rails, two-phase commit blocks on a coordinator and is impossible across organisations. A saga is a sequence of local transactions with compensations (a reversing entry, a hold release). Orchestrated sagas (Step Functions or a state-machine service) are easier to audit; choreographed ones (services reacting to events) suit simple fan-outs. Use explicit pending states so the customer sees the truth mid-flow.
Unknown outcomes
The hardest case is when you sent money to an external rail and the call timed out. Did it go through? Never assume it failed. Keep the transfer pending, query the rail's status with your reference, retry with the same idempotent reference, and let reconciliation settle it. Compensate only on a definitive failure.
Reconciliation is part of the design
Compare your ledger with external truth (network clearing files, rail statements, bank settlement reports) every day. Mismatches become exceptions with owners and SLAs. Say it in the interview: "The ledger is only right if it reconciles."
Card lifecycle: authorize, clear, settle
Authorization approves the purchase and places a hold; there's no ledger posting yet. Clearing (presentment) arrives later, typically within a few days, and creates the posting, matching and releasing the hold. Settlement moves funds between banks. Holds that never clear expire after a set number of days.
Explain it back
- Why must the idempotency record be written in the same transaction as the effect?
- What's the difference between a dual write and the outbox pattern?
- A transfer to another bank timed out. Walk through exactly what your system does next.
- Why don't authorizations create ledger entries?
Drills
D1 · Write the journal entries
For a credit card: (1) $50 purchase authorized; (2) it clears; (3) $2 foreign transaction fee; (4) the merchant refunds $50; (5) the customer pays $100.
Answer key
- No entry. Create a hold that reduces available credit by $50.
- Dr Cardholder receivable 50 / Cr Network settlement payable 50; release the hold.
- Dr Cardholder receivable 2 / Cr Fee income 2.
- Dr Network settlement payable 50 / Cr Cardholder receivable 50.
- Dr Cash (payment clearing) 100 / Cr Cardholder receivable 100. The account now shows a $48 credit balance.
D2 · Retry while in flight
A mobile client times out after 3 s and retries POST /transfers with the same key while the first request is still running. Design the behaviour.
Answer key
Insert the key with status IN_PROGRESS using a conditional put; the second request fails the condition and reads the record. If still in progress, return 409 with Retry-After (or wait briefly on it). When complete, return the stored response. If the first request crashed, a lease timeout on IN_PROGRESS lets a later retry take over safely, because the effect itself is also guarded by the transaction.
D3 · Design reconciliation
Design daily reconciliation between your card ledger and the network's clearing file.
Answer key
Ingest the file to S3 (immutable), parse to records, and match on network reference, amount and date. Outcomes: matched; in file but not in ledger (missing posting, so post it via the normal path); in ledger but not in file (possible duplicate or late file, so hold for N days); amount mismatch (exception queue). Track a break count and value SLI, alert on thresholds, and give exceptions an owner and an ageing report. Keep everything replayable and idempotent so you can re-run a day.
Watch and read
- ReadStripe · Designing robust and predictable APIs with idempotency
- Readmicroservices.io · Transactional outbox
- ReadChris Richardson · Managing data consistency with sagas (talk page and slides)
- VideoMartin Kleppmann · Distributed Systems 7.1: Two-phase commit
- VideoBanking Ledger: System Design Interview with a senior FAANG engineer
- VideoDesign a Payment System · System Design Interview
You're done when
Async and events
Capital One describes its direction as event-based and serverless-first. You need to choose the right kind of queue, pick partition keys that keep order where it matters, and make every consumer safe to run twice.
Core ideas
Queue, log or router
| Tool | Model | Use it for |
|---|---|---|
| SQS (standard) | Work queue, at-least-once, per-message ack, no replay | Background jobs, load levelling, one consumer group |
| SQS FIFO | Ordered per message group, deduplication window | Per-entity ordered commands at moderate volume |
| SNS | Push pub/sub | Fan-out to several queues or endpoints |
| EventBridge | Content-based routing, schema registry | Domain events across teams and accounts |
| Kinesis / MSK (Kafka) | Partitioned, ordered, retained log with replay | High-volume streams, many independent consumers, reprocessing |
Ordering is per key, and that's enough
Logs keep order only within a partition. Choose the key so the things that must stay in order share it: account_id for balance updates. If you think you need global ordering, you almost always need per-entity ordering plus timestamps.
Failures in consumers
Retry transient errors with exponential backoff and jitter. Send messages that keep failing to a dead-letter queue, with an alert and a redrive tool. On an ordered log, one poison message blocks its partition, so park it and move on if the business allows.
Backpressure and load levelling
A queue absorbs spikes so downstream systems see a steady rate. Watch the age of the oldest message; it's a better SLI than queue length. Scale consumers on it, but cap concurrency to protect the database behind them.
Designing events
Events are facts in the past tense (CardAuthorized, TransferSettled). Use an envelope: event ID, type, schema version, time, source, subject ID and correlation ID. Evolve schemas compatibly (add optional fields; never rename or repurpose). Keep PII out: carry tokens and IDs, not names and card numbers. Every topic has one owning team.
{ "id": "evt_01J…", "type": "card.authorization.approved", "version": 3,
"time": "2026-10-28T14:03:11.204Z", "source": "auth-service",
"subject": "acct_123", "correlation_id": "nw_8842…",
"data": { "auth_id": "au_…", "card_token": "tok_…", "amount": "50.00",
"currency": "CAD", "mcc": "5812" } }Event sourcing and CQRS, used sparingly
Event sourcing stores the events as the source of truth and derives state. It fits ledgers and audit-heavy domains, but it costs you replay tooling, schema evolution pain and harder queries. CQRS (separate write model and read models) is the lighter, more common win.
Explain it back
- When would you choose Kinesis over SQS, in one sentence?
- Why is age of oldest message a better alarm than queue length?
- What makes an event schema change safe?
Drills
D1 · Partition keys for two consumers
Transaction events feed (a) a balance updater that needs per-account order and (b) merchant analytics that aggregates by merchant. One stream or two? Which key?
Answer key
One stream keyed by account_id. Merchant analytics consumes it and re-keys into its own aggregation (stream processing or a merchant-keyed table). Don't key the main stream by merchant: large merchants become hot partitions and you lose per-account order.
D2 · No duplicate texts
A notification consumer calls an SMS vendor that fails transiently 2% of the time and is occasionally down for an hour. Customers must not get duplicate texts.
Answer key
Dedupe on the event ID in a table with a TTL before sending. Pass an idempotency key to the vendor if it supports one. Use exponential backoff with jitter and a capped number of attempts, then the DLQ. Add a circuit breaker that pauses consumption when the vendor is down; the queue holds messages and age-of-oldest alerts fire. Drop time-sensitive alerts that are too old (a "you spent $50" text arriving 6 hours late) instead of sending them.
D3 · Rename a field
A producer wants to rename amount to amount_minor_units and change it from a decimal string to an integer.
Answer key
That's a breaking change. Add the new field alongside the old one (version bump), have consumers migrate, check consumer adoption in the schema registry, and remove the old field only in a new major version or topic after a deprecation window.
Watch and read
You're done when
Reads, caching and traffic
Banks read far more than they write: balances, history, offers, dashboards. The skill is knowing what you may cache, how stale it can be, and how to protect the system when traffic surges.
Core ideas
Cache patterns
Cache-aside (the app reads the cache, falls back to the database, fills the cache) is the default. Read-through and write-through push that logic into the cache layer. Write-behind acknowledges before the database has the data; avoid it for money. Invalidate with TTLs for simple cases and with change events where staleness matters.
What not to cache in a bank
Never make a money decision from a cache. The authorization check and the transfer's funds check read the system of record. Caching is fine for display ("Available $1,240 · updated 20 s ago"), offer catalogs, product rules and reference data.
Stampedes and hot keys
When a popular key expires, thousands of requests miss at once. Use request coalescing (one loader, the rest wait), jittered TTLs and early refresh. For hot keys, replicate them across cache nodes or add a small in-process cache.
Rate limiting
A token bucket allows bursts up to the bucket size and a sustained rate equal to the refill rate. Limit at the edge (API Gateway usage plans per partner) and inside the service (per customer, per card). Distributed counters live in Redis or DynamoDB. Return 429 with Retry-After. For partners, limits belong in the contract.
Load balancing, pooling and pagination
Layer 7 load balancers route by path and header and do health checks. Pool database connections (RDS Proxy in front of Aurora when Lambda is the client). Paginate with cursors, not offsets, so deep pages stay fast and results don't shift.
Consistent hashing
Map keys and nodes onto a ring so that adding or removing a node only moves about 1/N of the keys. Virtual nodes even out the load. It's how cache clusters and many partitioned stores rebalance without a full reshuffle.
Drills
D1 · Cache or not?
Decide and give a TTL or invalidation rule for: (a) credit score shown on a monitoring dashboard, refreshed monthly; (b) available balance on the home screen; (c) available credit in the authorization path; (d) the card product catalog.
Answer key
- Cache aggressively (hours to days), invalidate on refresh events.
- Cache 15–60 s with an "as of" label, and evict on balance-change events.
- Never cache. Read the system of record atomically.
- Cache at the CDN and in the app for hours; invalidate on publish.
D2 · Token bucket for partners
Affiliate partners may send 100 requests a second sustained, with bursts to 200. Configure the bucket and say where it runs.
Answer key
Refill 100 tokens/s, capacity 200. Enforce at API Gateway with a usage plan per partner API key, plus a service-side limiter keyed by partner ID in Redis for finer limits (per endpoint). Return 429 with Retry-After, and publish limits in the partner contract and dashboards.
Watch and read
You're done when
Reliability, resilience and multi-region
Capital One has given at least five public talks on resilience. Expect to be pushed on what happens when things break, how fast you recover, and what that costs.
Core ideas
Timeouts, retries, backoff and jitter
Every remote call gets a timeout derived from the caller's own budget. Retry only idempotent operations, with exponential backoff plus random jitter so clients don't retry in lockstep. Retries multiply load at every layer, so retry at one layer and use a retry budget (for example, retries at most 10% of requests).
Circuit breakers, bulkheads and load shedding
A circuit breaker stops calling a failing dependency and fails fast. Bulkheads give each dependency its own pool so one slow call can't consume every thread. Load shedding rejects excess work early (cheaply) instead of accepting it and timing out everything.
Graceful degradation: hard vs soft dependencies
Decide which dependencies are hard (the request fails without them) and turn as many as possible into soft ones. If fraud scoring is slow, fall back to rules with a lower risk appetite. If the notification service is down, authorize anyway and send later. The fallback policy is a business decision; bring the business into it.
Limit the blast radius
Use multiple Availability Zones by default. Cells are independent copies of the stack, each serving a slice of customers, so a bad deploy or poison input hurts one cell. Shuffle sharding gives each customer a random subset of workers, so one noisy customer rarely takes down another. Separate AWS accounts add security and quota isolation.
Multi-region and recovery targets
RTO is how long you may be down; RPO is how much data you may lose. AWS describes four strategies from cheap and slow to expensive and instant (DR whitepaper). Choose per workload, not per company.
| Strategy | RTO | RPO | Cost | Fits |
|---|---|---|---|---|
| Backup and restore | Hours+ | Last backup | Lowest | Statement archive, internal reporting |
| Pilot light | Tens of minutes+ | Low (replicated data) | Low | Batch jobs, back-office tools |
| Warm standby | Minutes | Low | Medium | Credit monitoring, servicing apps |
| Active/active | Near zero | Near zero* | Highest | Authorization, login, payments |
*Data corruption still needs point-in-time backups, so true RPO for corruption is never zero.
Active/active without conflicts
Two regions writing the same balance will conflict. The usual pattern is a home region per account: all writes for an account go to its home, replicated asynchronously to the other region. On regional failure you move the home, accepting the replication lag as bounded risk (for example, capping authorization amounts during failover). Fail over with a data-plane mechanism you've practised (Route 53 Application Recovery Controller), not a console click, and design for static stability: the surviving region should already have the capacity it needs.
Most outages start with a change
Deploy with canaries by slice (a percentage of traffic, a BIN range), watch SLOs and business signals, and roll back automatically. Feature flags separate deploy from release. Capital One's Credit Offers API team used canary deployments to release around the clock without disrupting affiliates.
Observability and game days
Track RED metrics (rate, errors, duration) per service, distributed traces with a correlation ID from the edge, structured logs with no PII, and business SLIs. An approval-rate drop is often your first sign of an incident. Practise failure deliberately with chaos experiments and region-evacuation game days.
Drills
D1 · The slow dependency
Fraud scoring P99 jumps from 40 ms to 2 s at 12,000 authorizations a second. Walk through what happens with no timeout, then with your design.
Answer key
No timeout: in-flight requests go from about 1,800 to about 24,000 (Little's Law). Pools and concurrency run out, every authorization slows, network deadlines pass, stand-in kicks in and the incident spreads. With a 60 ms timeout, a circuit breaker and a rules-only fallback, authorization latency stays flat, approval behaviour shifts slightly under the fallback policy, an alert fires on the fallback rate, and the breaker probes recovery.
D2 · Choose DR strategies
Pick a strategy and RTO/RPO for: card authorization, customer login, monthly statements, credit score monitoring, the data warehouse.
Answer key
Authorization: active/active, RTO about 0, RPO about 0 for approvals with bounded-risk failover. Login: active/active. Statements: pilot light or backup and restore, RTO hours, since jobs re-run idempotently. Monitoring: warm standby, RTO minutes. Warehouse: backup and restore, RTO a day. Then say the cost logic: active/active only where an outage costs real money or trust.
D3 · Failover runbook outline
Outline a regional failover runbook for authorization.
Answer key
- Detection: health and SLI alarms, including approval rate and stand-in rate.
- Decision: who declares, and the criteria for it.
- Fence the failed region (stop writes) through the recovery controller.
- Move home-region designations; enable the failover limits policy.
- Verify with synthetic transactions and dashboards.
- Communicate internally and to the network.
- Fail back: reconcile writes, check data, move homes back gradually.
- Rehearse quarterly; keep evidence for the regulator.
Watch and read
- VideoAWS re:Invent 2023 · Capital One: Achieving resiliency to run mission-critical applications (FSI314)
- VideoAWS re:Invent 2025 · Building resilient multi-Region applications with Capital One (ARC404)
- VideoAWS re:Invent 2022 · Benefiting from chaos engineering at Capital One (PRT026)
- VideoAWS re:Invent 2020 · Architecting resiliency, recovery, and reliability: Capital One
- VideoAWS re:Invent 2018 · How AWS Minimizes the Blast Radius of Failures (ARC338)
- VideoAWS re:Invent 2023 · Best practices for creating multi-Region architectures (ARC308)
- ReadAmazon Builders' Library · Timeouts, retries, and backoff with jitter
- ReadAmazon Builders' Library · Using load shedding to avoid overload
- ReadAmazon Builders' Library · Workload isolation using shuffle-sharding
You're done when
Security, privacy and compliance architecture
In a bank, security isn't a section at the end; it shapes the boundaries of the design. Your HIPAA and GDPR work transfers directly. Learn the Canadian banking vocabulary and the card-data specifics.
Core ideas
Threat model the data flow
Draw the flow, mark trust boundaries, then walk STRIDE at each boundary: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege. Ten minutes of this in an interview reads as senior.
Identity everywhere
Customers: OpenID Connect, MFA, device binding, and step-up authentication for risky actions (adding a payee, changing a phone number). Services: short-lived credentials (IAM roles, mTLS), never shared secrets. Staff: SSO and just-in-time access with approval and logging. Authorization: role- or attribute-based, ideally in a policy engine you can audit.
Least privilege: the 2019 lesson
Capital One's 2019 breach is public and well analysed: a misconfigured web application firewall allowed server-side request forgery, which reached the EC2 instance metadata service and obtained credentials for a role with broad S3 access (KrebsOnSecurity). Architectural lessons: narrowly scoped roles, IMDSv2, egress controls, S3 access through VPC endpoints and bucket policies, and anomaly detection on data access. Capital One also created Cloud Custodian, an open-source policy-as-code tool for cloud guardrails. Mention lessons respectfully and briefly if it's relevant.
Protecting data
TLS in transit. Encryption at rest with KMS. Envelope encryption: a data key encrypts the data and a KMS key encrypts the data key, so keys rotate cheaply and access is auditable. Field-level encryption for the most sensitive fields. Tokenization replaces the card number with a token so most systems never touch real card data, which shrinks PCI DSS scope; encryption alone doesn't (Stripe). Classify data, minimise it, and mask it in logs.
Audit trails
Record who did what, when, from where, and why the system decided. Make records immutable (S3 Object Lock, a separate security account), keep them for the retention period, and make them searchable for investigations.
The Canadian regulatory map
| Rule | What it means for a design |
|---|---|
| OSFI B-13 (technology and cyber risk, in effect since January 2024) | Governed change management, tested resilience and recovery plans, cyber controls, incident reporting. |
| OSFI B-10 (third-party risk) | Cloud and vendors: concentration risk, audit and log access, exit plans. Relevant to every build-versus-buy decision. |
| PIPEDA (and Quebec's Law 25) | Consent, purpose limitation, safeguards, breach reporting, access requests. |
| PCI DSS | Card data: segmented scope, tokenization, key management, logging, testing. |
| Model risk management (OSFI E-23) | Inventory, validation and monitoring for decision models, including AI. |
Summary from Torys on B-13 and general knowledge. In an interview, say you'd confirm specifics with the compliance team, and design so residency and retention are configuration, not code.
Drills
D1 · STRIDE on "change my phone number"
A customer changes the phone number on their account in the mobile app. Threat-model it.
Answer key
Spoofing: a stolen session changes the number to take over the account, so require step-up auth (one-time code to the old number or a biometric) and check device risk. Tampering: sign the request and validate on the server. Repudiation: audit with device and IP. Information disclosure: mask numbers in the UI and logs. Denial of service: rate-limit attempts. Elevation: authorize on the account, not on IDs in the request. Also notify the old number and email, and apply a cooling-off period on high-risk actions (like adding a payee) after a contact change.
D2 · Review this proposal
"We'll store full card numbers in our app's PostgreSQL database, encrypted with AES-256. The key is in the app's config file."
Answer key
Reject as proposed. The key sits next to the data, so anyone with app config has everything. The app and its database fall fully into PCI scope. There's no key rotation or access audit. Alternative: use the tokenization vault and store tokens only. If the app truly needs card numbers, use KMS envelope encryption with a narrowly scoped role and CloudTrail auditing, inside the PCI zone.
Watch and read
You're done when
AWS solution architecture
The posting requires at least two years of public-cloud solution architecture and prefers AWS. Capital One closed its last data centres in 2020 and runs on AWS. Speak AWS fluently, and translate your GCP work without apology.
Core ideas
Building blocks and when not to use them
| Need | AWS choice | Watch out for |
|---|---|---|
| Edge and APIs | CloudFront, WAF, API Gateway, ALB | API Gateway limits and cost at very high request rates; ALB for steady container traffic |
| Event-driven compute | Lambda | Cold starts, concurrency quotas, database connections (use RDS Proxy), cost at steady high throughput |
| Services | ECS on Fargate; EKS for platform teams | Fargate is simpler; EKS adds power and operational cost |
| Relational | Aurora PostgreSQL (Global Database) | Single writer per cluster; plan the failover |
| Key-value | DynamoDB (global tables) | Model access patterns first; hot partitions; conflict behaviour across regions |
| Integration | SQS, SNS, EventBridge, Kinesis, MSK, Step Functions | See Module 4; Step Functions Express vs Standard for short vs long workflows |
| Security | IAM, KMS, Secrets Manager, GuardDuty, Security Hub, Macie | Least privilege by default; separate security and log accounts |
| Operations and resilience | CloudWatch, X-Ray, CloudTrail, Config, Route 53 ARC, Fault Injection Service | Practise failover; alarms on SLIs, not only CPU |
Serverless-first, with judgment
Capital One has spoken publicly about a serverless-first approach. Serverless wins on spiky, event-driven work and removes server management. Containers win for steady high throughput, long-lived connections (voice streams, WebSockets) and tight latency control. Say it as a rule of thumb, then apply it.
Accounts, networks and guardrails
Use a multi-account landing zone (accounts per workload and environment), service control policies, and central logging and security accounts. Use private subnets and VPC endpoints so traffic to AWS services stays off the internet, with controlled egress. Express guardrails as policy-as-code.
Well-Architected as your review lens
The six pillars (operational excellence, security, reliability, performance efficiency, cost optimization, sustainability) make a good checklist for the final minutes of a design and for ARB reviews.
Cost as a design input
Talk in unit economics: cost per authorization, per statement, per active customer. Watch data transfer (cross-AZ, cross-region, NAT gateways), over-provisioning and logging volume.
Translate your GCP experience
| You used (GCP) | Say on AWS |
|---|---|
| Cloud Run | ECS on Fargate, App Runner or Lambda |
| Cloud SQL | RDS or Aurora PostgreSQL |
| GKE | EKS |
| Pub/Sub | SNS + SQS, EventBridge, or Kinesis |
| Cloud Tasks / Workflows | SQS / Step Functions |
| Memorystore | ElastiCache |
| Firestore | DynamoDB |
| BigQuery | Redshift or Athena on S3 |
| Cloud Storage | S3 |
| Cloud KMS / Secret Manager | KMS / Secrets Manager |
| Cloud Logging and Monitoring | CloudWatch, X-Ray |
| Cloud Armor | WAF and Shield |
Drills
D1 · Re-draw Roseline on AWS
Take your Roseline architecture (Cloud Run, Cloud SQL, Redis, Twilio/LiveKit, LLM providers) and design it on AWS for a bank's call centre. Explain each choice.
Answer key
Voice gateway on ECS Fargate (long-lived WebSockets, steady latency). Conversation state in ElastiCache. Aurora PostgreSQL for appointments and audit. Tool calls through an internal API layer with scoped IAM roles per tool. Models through Amazon Bedrock or an approved gateway with PII redaction. Transcripts in S3 with KMS and Object Lock where policy requires. EventBridge for post-call events. CloudWatch and X-Ray with a correlation ID per call. Evaluation gates in the deployment pipeline.
D2 · Lambda or Fargate?
A synchronous API with a 2 s SLA at a steady 5,000 requests a second, 24/7, calling Aurora.
Answer key
Lean Fargate. The load is steady, so you won't benefit from scaling to zero; Fargate keeps connection pools warm against Aurora and gives predictable latency, and it's likely cheaper at this volume. Lambda works with RDS Proxy and provisioned concurrency, but you'd be paying to remove its advantages. Write it as an ADR and revisit if the load becomes spiky.
Watch and read
- VideoAWS re:Invent 2019 · Serverless at scale (SVS219)
- VideoAWS re:Invent 2023 · AWS On Air ft. Capital One's journey to AWS Lambda serverless architecture
- VideoAWS re:Invent 2022 · Journey to cell-based microservices architecture on AWS (ARC312)
- ReadCapital One Tech · A serverless and Go journey: Credit Offers API
- ReadSiliconANGLE · Capital One closes all data centres to rely on AWS
- ReadAWS Well-Architected Framework · The pillars
Close the certification gap
The posting lists AWS Solutions Architect – Associate as preferred. Booking the exam for week 8 or 9 gives this module a deadline and puts a credential on your resume.
You're done when
The architect's toolkit: HLDs, ADRs, review boards and migration
The posting lists these as the job: create and review ADRs and HLDs, sit on the Architecture Review Board, keep a capability catalog, work with business partners. Most candidates never practise them. You will.
Core ideas
The anatomy of a high-level design
- Context and goals: the business outcome, with a measure.
- Scope and non-goals.
- Requirements: functional, and non-functional with numbers.
- Context and container diagrams (C4 levels 1–2).
- Key flows as sequence diagrams.
- Data: ownership, classification, retention, residency.
- Interfaces: APIs and events, with contracts and versioning.
- Security and compliance: threat-model summary and controls.
- Resilience: SLOs, RTO and RPO, DR strategy, failure modes.
- Operations: observability, runbooks, on-call ownership.
- Cost: unit economics and growth.
- Rollout and migration: transition states, rollback.
- Risks and open questions, each with an owner.
- Decisions: links to the ADRs.
Architecture decision records
Michael Nygard's format (2011) is short: Title, Status, Context, Decision, Consequences (arc42 FAQ, Martin Fowler). Most teams add options considered and the criteria used. One decision per record. Records are immutable: you supersede an ADR, you don't edit it.
ADR-014 Per-account conditional writes in DynamoDB for authorization holds Status Accepted · 2026-03-02 · supersedes ADR-006 Context Authorization must decide within 150 ms (P99) at 10,000 TPS peak and must never over-commit available credit under concurrent requests. Analytics needs ad-hoc queries on holds. Corporate accounts may reach 2,000 TPS on one limit. Options considered A. Aurora PostgreSQL, SELECT … FOR UPDATE per account B. DynamoDB conditional UpdateItem + TransactWriteItems (account, hold, decision) C. In-memory single-writer actor per account, journaled to a log Criteria correctness · P99 latency · horizontal scale · operability · team skills Decision B. Partition key = account_id. Each approval is one transaction that decrements open-to-buy if sufficient, writes the hold and writes the decision record. Hot accounts use credit tranches (ADR-015). Consequences + Single-digit-ms writes; scales by account; no lock contention across accounts. + Conditional writes make over-commit impossible within a region. − No ad-hoc SQL: analytics reads DynamoDB Streams into the lake. − Transactions are limited to 100 items; batch operations need care. − Cross-region: home-region writes required (ADR-016). Review Revisit if hot-account traffic exceeds 20% of volume.
How review boards work, from both sides
As presenter: in 10 minutes cover the problem, the constraints, two or three options, your decision, the risks and what you need from the board. As reviewer: check the requirements trace to the design, the failure modes and recovery targets, security and data classification, standards and exceptions, cost, and operational ownership. Prefer approve with conditions and time-limited exceptions over rejection. Automate standards as fitness functions and policy-as-code so the board reviews judgment, not checklists.
Capability maps
A business capability is what the business does, independent of how: acquire customers, underwrite, issue cards, authorize, post transactions, bill, collect, service customers, report to regulators. Map systems onto capabilities to see duplication, gaps and where investment should go. Shared capabilities (decisioning, identity, notifications) become platform candidates.
Build or buy
Score options on differentiation (does this make us better than competitors?), time to value, total cost over 3–5 years, integration effort, regulatory and vendor risk (OSFI B-10), and exit cost. Buy the commodity, build the differentiator, and always write down the exit plan.
Migrating without a big bang
The strangler fig pattern routes traffic through a facade and moves capability by capability from legacy to new (Fowler). Supporting techniques: change data capture to keep data in sync, parallel run with automated diffing, dark launches, feature-flag cutover by slice, and a rollback for every step. Think in transition architectures, each a stable state you could stop at.
Communicating at every level
Gregor Hohpe's "architect elevator": the same decision told three ways. To executives, talk risk, cost and time to market. To product, talk capabilities and options. To engineers, talk the design and its trade-offs. Practise telling one decision all three ways.
Drills
D1 · Write two ADRs (90 minutes)
(1) Your EMR integration decision: hybrid native APIs plus browser automation. (2) Lambda vs Fargate for the Credit Offers partner API. Use the template above. Have someone read them cold and tell you the decision in one sentence; if they can't, rewrite the Decision section.
D2 · ARB simulation
Present your AWS-to-GCP migration to an imaginary board in 10 minutes, then answer these as the board: Why not stay? What was the exit plan? How did you prove data parity? What's the vendor concentration risk now? What would make you reverse it?
D3 · Capability map
Draw a level-1 capability map for Capital One Canada's credit card business. Mark where Quick Check, the Credit Offers API and the application flow probably share a decisioning capability.
Answer key
Level 1: Market and acquire → Pre-qualify and decide → Onboard and verify identity → Issue cards → Authorize → Post and settle → Bill and pay → Collect → Service and dispute → Manage risk and fraud → Report and comply. Quick Check, the Credit Offers API and applications all use "Pre-qualify and decide". That's a platform candidate with one policy engine and one audit trail, which also guarantees that pre-approval and application decisions agree.
Watch and read
- VideoSimon Brown · Visualising software architecture with the C4 model (Agile on the Beach 2019)
- VideoGregor Hohpe · The Architect Elevator: Connecting Penthouse and Engine
- ReadMartin Fowler · Architecture Decision Record
- ReadMartin Fowler · Strangler Fig
- ReadWill Larson · Staff engineer archetypes (the "architect" archetype)
You're done when
ML and AI systems in a bank
Capital One integrates machine learning and conversational AI into core operations, and a reported Senior+ question asks for an agent platform with guardrails. This is where your Roseline and Curacel work becomes an advantage. Frame it as architecture and governance, not model tuning.
Core ideas
Real-time scoring on a latency budget
The model call is usually the smallest part. Getting fresh features is the hard part: real-time aggregates (spend in the last hour, new merchant, distance from the last transaction) come from stream processing into an online store, and historical features come from an offline store. Keep training and serving features identical (one feature definition, two materialisations) to avoid training-serving skew. Always have a rules-only fallback.
Fraud is a loop, not a model
Rules and models make the decision; case management handles reviews; confirmed fraud and chargebacks come back weeks later as labels. Thresholds are business decisions trading fraud loss against declined good customers. Run new models in shadow mode first, then champion/challenger.
Model governance
Regulated banks keep a model inventory, validate models independently, document them, monitor drift and performance, and can reproduce any decision (inputs, feature versions, model version, threshold). Your ML platform's data versioning and reproducible experiments are exactly this.
Agent and LLM platforms with guardrails
A gateway in front of all model calls handles authentication, routing, PII redaction, cost and latency budgets, caching and logging. Retrieval respects the user's entitlements. Tools are explicit, versioned APIs with least-privilege scopes; risky tools need confirmation or human approval. Inputs and outputs pass policy checks (prompt injection, data leakage, unsafe advice). Every turn is audited. Evaluation suites gate every model or prompt change, with a clear human handoff path. These are all things you built for Roseline; present them as a platform.
Drills
D1 · Agent platform with guardrails (45-minute mock)
"Design an enterprise agent platform that many teams use to build assistants, with guardrails and latency control." Draw the platform, then walk through one customer-service conversation that calls a "freeze card" tool.
What a strong answer covers
- Platform vs team responsibilities: what's shared, what each team owns.
- A gateway with model routing, quotas, budgets and fallbacks.
- A tool registry with scopes; "freeze card" needs step-up confirmation and an idempotency key.
- Retrieval that enforces entitlements; PII redaction before third-party models.
- Policy checks on input and output; human escalation.
- Evaluation gates and regression suites; shadow traffic; per-turn audit.
- A latency budget for streaming responses; per-conversation cost.
- Model risk governance and an inventory.
D2 · Fraud on a 50 ms budget
Design card-not-present fraud scoring inside a 50 ms budget. Which features are precomputed, which are computed live, and what happens at 51 ms?
Answer key
Precomputed (online store, one read, ~5 ms): customer profile, velocity aggregates updated by the stream, device and merchant reputation. Live (in-process, ~2 ms): amount against usual, time of day, new-merchant flag. Model inference: ~10–20 ms on a warm container. At 51 ms the authorization service times out and applies the rules-only fallback, logs it, and the fallback-rate SLI counts it.
Watch and read
You're done when
Communication, whiteboarding and the mock loop
The same design scores very differently depending on how it's delivered. This week is almost all practice: full mocks, recorded, scored and repeated.
Core ideas
Draw while you talk, and draw less
Use the same layout every time (left to right, numbered steps, keys next to stores). Name boxes by responsibility ("Authorization orchestrator"), not technology, until you've decided the technology. If the interview is virtual, practise in the tool they're likely to use (Excalidraw or a shared whiteboard) until drawing costs you no attention.
Check in every five minutes
Ask a short question that hands the interviewer a choice: "I can go into the ledger model or the failover next. Which is more useful?" You stay in control while showing you're collaborating.
Handle curveballs calmly
When the interviewer changes a requirement ("now it must work across two countries"), restate it, say which part of the design it affects, and adjust only that part. When you don't know something, say what you'd verify and why the design doesn't depend on it.
Recover from mistakes in the open
"I said we'd cache balances; that's wrong for the transfer path, because the decision must read the ledger. Let me fix that." Correcting yourself openly is a strong signal at this level.
Your own system as an HLD
Prepare a 10-minute talk on one of your systems using the HLD outline from Module 9: context, a container diagram, one sequence, three decisions with trade-offs, one failure you lived through, and what you'd change. Rehearse until it's 9 minutes.
The mock loop protocol
- Draw a question in the Practice lab (filter to Reported and Architecture for the final week).
- Start the 45-minute timer and record your screen and voice.
- Afterwards, score yourself on the rubric before watching the recording, then again after. The gap shows your blind spots.
- Write down the three moments you'd redo. Redo the weakest one immediately.
- Mark the question Practiced. Revisit it on day 3, 7 and 14; mark it Solid when it scores 3+ everywhere.
Mock with people as often as you can. A peer at staff level, a former colleague, or a paid service such as Hello Interview will see things you can't. Ask them to interrupt you and change requirements.
Watch
- VideoByteByteGo · How to Crack Any System Design Interview
- VideoSenior System Design Mock Interview: Design an e-commerce platform
- VideoSenior/Staff System Design Mock Interview: Design Online Auction
Watch mocks actively: pause at each stage and say what you would do before the candidate does.
You're done when
Design a credit card authorization system
The issuer's side of a card purchase: a request arrives from the card network, and you approve or decline within the network's deadline without ever over-committing credit.
Attempt it first
Run the full 45 minutes on this question before reading further. Then compare, stage by stage. The value is in the gap between your attempt and this one.
Frame
Questions to ask: credit cards only? Card-present and online? Tokenized wallets? Do we own fraud scoring or call a platform? How many regions?
Assumptions to state: 10 million active cards. 2,000 authorizations a second on average, 10,000 at peak. P99 of 150 ms inside our systems; the network's deadline is hard, and beyond it the network may apply stand-in processing using limits we set. 99.99%+ availability. Every decision auditable for seven years.
The crux, said out loud
"Three hard parts: available credit must never be over-committed by concurrent authorizations; fraud scoring has to fit inside the latency budget; and if we're unavailable, the network decides for us, so availability is direct financial risk."
Shape
Entities: Card (token; the real number lives in the vault), Account (credit limit, posted balance, open-to-buy), Authorization (auth_id, network_txn_id, amount, merchant, MCC, status, expiry), Hold, DecisionRecord (inputs, rules fired, model version, score).
POST /authorizations (internal; the gateway translates ISO 8583)
{ network_txn_id, card_token, amount, currency,
merchant: { id, mcc, country }, entry_mode, wallet_token? }
→ { decision: APPROVE|DECLINE|PARTIAL, auth_code, reason_code }
POST /authorizations/{id}/reversals
POST /advices (stand-in decisions the network made for us)
Numbers that matter: 10,000/s × 150 ms ≈ 1,500 in flight. At about 3 KB per decision that's ~30 MB/s of writes at peak, so we partition by account from day one.
Sketch
After authorization, clearing files arrive within days. The posting service matches each cleared item to its authorization, writes ledger entries and releases the hold. A scheduler expires holds that never clear.
Deepen
1 · Available credit without over-commit
Options: a row lock in Aurora, a DynamoDB conditional write in a transaction, or an in-memory single writer per account. Decision: DynamoDB keyed by account_id, with one TransactWriteItems that conditionally decrements open-to-buy, inserts the hold and writes the decision record. It's correct by construction, takes single-digit milliseconds and scales by account. Accepted cost: no ad-hoc SQL, so analytics reads the change stream. Hot accounts: credit tranches across sub-items (Module 2, drill 3).
2 · The latency budget
| Step | P99 budget |
|---|---|
| Gateway parse and route | 5 ms |
| Tokenize card number | 5 ms |
| Parallel: account state, card controls, fraud score | 60 ms (fraud timeout) |
| Decision rules | 5 ms |
| Atomic reserve and write | 15 ms |
| Respond | 5 ms |
| Total, with ~55 ms headroom | ~95 ms |
No synchronous cross-region calls on this path, and no synchronous calls to anything that isn't needed for the decision.
3 · When fraud scoring is slow or down
Fraud is a soft dependency: a 60 ms timeout and a circuit breaker, then a rules-only fallback with a stricter risk appetite (for example, lower approval ceilings for high-risk merchant categories). The business owns the fallback policy; engineering owns the switch and the alert on fallback rate.
4 · Multi-region
Options: synchronous cross-region writes (correct, but every authorization pays the round trip), or a home region per account with asynchronous replication. Decision: home-region writes, with both regions receiving network traffic and forwarding to the home region. On regional failure the survivor takes over homes and applies a failover limits policy (caps on amounts) to bound the exposure from replication lag. This turns a consistency problem into a bounded, business-approved financial risk, the same idea as network stand-in. Rehearse it with game days.
5 · Idempotency and odd ordering
The network retries with the same network_txn_id, so return the original decision. A reversal can arrive before its authorization; store a reversal marker so the late authorization is cancelled on arrival. Stand-in advices are applied as holds after the fact, and reconciled.
6 · Audit and security
The decision record (inputs, rule-set version, model version, feature snapshot hash, decision) streams to S3 with Object Lock for seven years. The PCI zone is as small as possible: the gateway and the vault. Card verification cryptography runs in HSMs. Services use mTLS and narrowly scoped roles.
Harden and wrap
- SLIs: P99 decision latency; success rate; approval rate against its forecast (a sudden drop is often the first sign of an incident); stand-in rate; fraud fallback rate.
- Deployment: canary by a percentage of traffic or BIN range; automatic rollback on approval-rate deviation.
- At 10×: hot corporate accounts, fraud model latency, stream throughput.
Follow-ups to rehearse
| They ask | A strong answer includes |
|---|---|
| Two authorizations for the same account arrive in different regions at once. | Both route to the home region; the conditional write serialises them. |
| How do partial approvals work? | Approve up to open-to-buy when the merchant supports it; the hold is for the approved amount. |
| What if a hold never clears? | Expire it after N days by merchant category (hotels and car rentals last longer); release open-to-buy; emit an event. |
| How do you test failover? | Regular game days in production with traffic shifted gradually; synthetic transactions; evidence kept for audit. |
| How does a customer's card lock take effect? | Card controls in the same partitioned store; read on every authorization, so it takes effect immediately. |
Design a banking system: accounts, transfers and history
The most reported Capital One question across levels. Scale isn't the hard part; correctness, auditability and honest handling of external rails are.
Attempt it first
This was your Week 1 baseline. Re-attempt it now, then compare.
Frame
Scope: deposit accounts; transfers between accounts at the bank; transfers to other banks through a payment rail; transaction history. Out of scope: lending and cards.
Assumptions: 5 million customers, 10 million transactions a day (~115/s average, ~1,000/s peak). History kept for seven years. 99.95% availability for transfers, 99.9% for history.
The crux
"Money must never be lost, duplicated or half-applied; every balance must be explainable from its entries; and transfers to other banks have outcomes we don't control. At this volume one well-designed ledger database is enough, so I'll spend the time on correctness, not sharding."
Shape
Entities: Customer, Account, LedgerAccount (including internal accounts such as outgoing-transfers clearing), JournalEntry with EntryLines, Transfer (a state machine: CREATED → FUNDS_HELD → SENT → SETTLED, or FAILED → REVERSED), IdempotencyRecord.
journal_entries (entry_id PK, transfer_id, created_at, description)
entry_lines (entry_id FK, ledger_account_id, direction D|C, amount, currency)
-- invariant per entry: Σ debits = Σ credits
balances (ledger_account_id PK, posted, available, version)
-- updated in the same DB transaction as the lines
transfers (transfer_id PK, idempotency_key UNIQUE, state, external_ref, …)
outbox (event_id PK, payload, created_at, published_at)
Sketch
Deepen
1 · Internal transfers need no saga
When both accounts are in the same ledger database, a transfer is a single journal entry in a single transaction. Keeping the ledger in one consistency boundary for as long as scale allows is the simplest correct design. Say this explicitly; it shows you aren't distributing for its own sake.
2 · Concurrency on one account
Use a conditional update on available balance (or SELECT … FOR UPDATE on the balance row), in the same transaction as the entry lines and the idempotency record.
3 · External transfers
An orchestrator (Step Functions Standard, or a state-machine service) holds funds by moving them to an outgoing clearing account, calls the rail with our reference as the idempotency key, and records the outcome. On "unknown" it polls status and waits for reconciliation; it compensates (reverses the entry) only on a definitive failure. The customer sees "Pending" throughout.
4 · History and read models
The outbox feeds a history read model (DynamoDB keyed by account and time) and search. Reads are eventually consistent, with the customer's own new transfer shown immediately from the transfer response (read-your-writes in the client).
5 · Reconciliation and audit
Reconcile the clearing account against the rail's settlement report daily; its balance should return to zero as transfers settle. Every entry carries who initiated it and why; entries are immutable, and corrections are new entries.
6 · When this outgrows one database
Scale vertically and add read replicas first. Next, partition ledger accounts by customer with an internal inter-partition clearing account, so cross-partition transfers become two local entries plus a saga. Mention that purpose-built ledger databases exist, and that you'd evaluate build versus buy.
Design Quick Check: card pre-approval without affecting a credit score
The posting describes Quick Check as giving customers "100% certainty which credit cards they'll be approved for before they apply". That promise is the crux: the pre-approval and the real application must agree.
Frame
Flow: a prospect enters identity and income details and consents; we run a soft credit inquiry (which doesn't affect their score) with a bureau; a decision engine evaluates each card product; we show the products they're pre-approved for; later they apply.
Assumptions: 50,000 checks a day, campaign peaks around 50 per second. The bureau call dominates latency (assume 1–3 s); target under 5 s end to end. 99.9% availability.
The crux
"Honouring the certainty promise; depending on an external bureau we don't control; handling sensitive personal and credit data under consent; and making every decision explainable."
Sketch
Deepen
1 · Keeping the promise
Each pre-approval produces a signed offer token that binds the decision, the policy version and a hash of the inputs, with an expiry. At application, the same engine re-evaluates using the token's policy version. If the applicant's information matches, the outcome matches. The promise's conditions (information unchanged, identity verified) are written into the policy and the customer disclosure.
2 · The bureau as a dependency
A bureau gateway wraps vendor APIs with timeouts, a circuit breaker and contract tests. Options during an outage: tell the customer to try later, or accept the request and notify them by email when it completes. Using a second bureau for resilience means a second policy calibration, so it's a business decision with B-10 third-party-risk implications.
3 · Personal data
Collect the minimum. Record consent with purpose and timestamp. Encrypt bureau responses at field level and keep them only as long as policy allows. Restrict access to the decision service. Analytics uses de-identified data.
4 · Explainable, governed decisions
Store reason codes for every product decision. Policy and model changes go through governance (model risk review and the architecture board) and roll out by version, so any past decision can be reproduced. Experiment freely on presentation; changes to eligibility need approval.
5 · Capability reuse
The same decisioning capability serves the Credit Offers API for affiliates (partner authentication, rate limits, data minimisation in responses). That's a capability-catalog point worth making in an architect interview.
Design credit score monitoring with change alerts
Modelled on Credit Keeper: millions of enrolled customers, scores refreshed on a schedule from a bureau, a dashboard and alerts when something changes. The crux is a large, vendor-limited batch that must be idempotent, then a notification fan-out that never spams.
Sketch
Key decisions
- Scheduling: spread refreshes across the month by customer hash, instead of all on day 1, to respect bureau quotas and smooth cost. Each batch item is keyed by (customer, period), so re-runs are idempotent.
- Storage: raw responses in S3 (encrypted, with retention rules); score history in DynamoDB keyed by customer and period; insights computed once per refresh.
- Change detection: compare with the last period; emit
ScoreChangedandNewInquiryDetectedevents. - Notifications: preferences, quiet hours, dedupe on event ID, one digest instead of five pushes.
- Dashboard: heavily cached (the data changes monthly), so it can run warm standby rather than active/active.
- Cost: bureau fees dominate; model cost per active user and stop refreshing customers who have been inactive for a long time (a product decision).
Move nightly batch card posting to near-real time
An architecture case rather than a design question: customers see purchases as pending until the next day because posting runs in a nightly batch on a legacy platform. The batch window is shrinking and change is risky. Lead the move without a big-bang cutover.
How to run this case in an interview
- Current state and pain, with measures: posting latency T+1, batch window, change failure rate, cost.
- Target state: an event-driven posting service, ledger and read models.
- Transition architectures, each stable enough to stop at.
- Decisions as a list of ADRs.
- Risks, governance checkpoints and success metrics.
Transition plan
| Stage | What changes | Exit criteria | Rollback |
|---|---|---|---|
| T0 · Mirror | CDC from the legacy database into a stream; build read models. Customers see posted items sooner from the mirror. | Mirror lag under 1 minute; reconciliation shows parity. | Turn off the new read path. |
| T1 · Shadow | The new posting engine processes the same clearing input in parallel; the diff service compares results daily. | Zero unexplained differences for 30 days. | Nothing to roll back; legacy is still the system of record. |
| T2 · Slice cutover | Move portfolios slice by slice (BIN ranges) to the new engine as system of record; feed legacy downstream consumers back from the new side. | SLOs met; reconciliation clean; downstream reports verified. | Flip the slice back by flag; replay from the stream. |
| T3 · Retire | Migrate the remaining downstream consumers (statements, regulatory reports), then decommission the batch. | Every consumer inventoried and migrated; audit evidence archived. | Keep legacy read-only for one cycle. |
Decisions, risks and measures
ADRs to write
- Event backbone and partition key
- Ledger store and posting model
- System of record during transition
- Diff and reconciliation method
- Slice strategy and rollback
Risks
- Hidden downstream consumers of batch files: inventory them first
- Cost of running both systems
- Data drift during the parallel run
- Change governance under OSFI B-13: keep evidence of every cutover
Success measures
- Posting latency from T+1 to under 5 minutes
- Reconciliation breaks below an agreed threshold
- Zero customer-impacting incidents at cutover
- Legacy cost reduction after T3
Practice lab
A mock timer, a self-scoring rubric and four drill decks. Use the timer for every full attempt; use the decks for 10-minute sessions on busy days.
Mock interview timer
The current stage is highlighted. When a stage runs over, move on.
Score yourself
Score straight after the mock, then again after watching your recording. 1 = missing, 2 = present but shallow, 3 = solid, 4 = what a Senior Staff interviewer hopes to see.
Deck 1 · Trade-off duels
Decide in 60 seconds, out loud, with one reason and one accepted cost. Then reveal.
Deck 2 · Failure injection
Pick a design you've practised. Read a card and explain, in two minutes, what your system does: detection, impact, mitigation, recovery.
Deck 3 · Review this design
You're on the Architecture Review Board. A team submits this proposal. Find at least eight problems, rank them, and decide: approve, approve with conditions, or reject.
Proposal: "Instant transfers v1"
The mobile app calls API Gateway, which invokes a Lambda function. The function reads the sender's balance from ElastiCache; if it's enough, it writes a debit to DynamoDB table Accounts, then a credit to the recipient's row, in two separate calls. It then publishes a TransferCompleted message to SNS. On any error it retries five times immediately. For debugging, it logs the full request, including account numbers and names. There's no idempotency key; the app disables the button after a tap. The history screen scans the Transactions table filtered by account. Everything runs in one region; backups are nightly.
Reviewer's answer key
- Funds check against a cache: stale balances allow overdrafts. Read and decrement atomically at the source of truth.
- Two separate writes: a crash between them destroys or creates money. Use one journal entry in one transaction (DynamoDB TransactWriteItems, or a ledger database).
- No idempotency: a disabled button doesn't stop network retries. Require an idempotency key stored with the effect.
- Dual write to SNS: the event can be lost or describe something that didn't happen. Use an outbox or the change stream.
- Immediate retries ×5: retry storms, and duplicate effects without idempotency. Use backoff with jitter, retry only idempotent calls, and set a budget.
- PII in logs: a privacy and compliance violation. Use structured logs with tokens and masking.
- Scan for history: cost and latency grow with the table. Key by account and time.
- One region, nightly backups: RPO up to 24 hours for money movement is unacceptable. Define RTO/RPO; use point-in-time recovery and a multi-region strategy.
- No ledger or audit trail: balances can't be explained or reconciled.
- No step-up or fraud checks for new recipients or large amounts.
Verdict: reject as submitted, and offer a working session. Items 1–4 are correctness defects, not style. Approve-with-conditions is right only once the money path is fixed.
Deck 4 · Estimation sprints
Spaced repetition
Revisit questions marked Practiced on day 3, day 7 and day 14 after the first attempt. Each revisit is a 15-minute speed run: frame, sketch and name the crux and three decisions. Move a question to Solid when you can do that without notes and the full mock scores 3 or above. Re-run one Solid question a week so it stays fresh.
Your systems, your stories
At this level, interviewers test your judgment by digging into systems you built. Prepare each story as a small decision record: context, options, decision, outcome, and what you'd change.
Check every detail against what actually happened
Several resume bullets were drafted from short notes: the options weighed for the EMR operator, the architecture review process, the shared platform capabilities and the migration's parallel run. Before you interview, make sure each detail matches what you actually did, and change the resume where it doesn't. Interviewers at this level will ask follow-up questions until they find the edge of your real experience.
Story cards
The "AWS shop" question
You moved Ubenwa from AWS to GCP. At an all-in AWS company, expect "Would you do that again?" Prepare a balanced answer: the business reason (startup credits extended runway), how you limited risk (parallel run), what it cost (migration time, re-learning, switching costs), and what you'd do differently (more portable infrastructure as code, a clear exit plan). Then pivot: your AWS architecture work at Ubenwa and Curacel, and that you know both clouds well enough to choose deliberately.
Behavioral questions with an architecture edge
| Question | Pick a story where you… |
|---|---|
| An architecture decision that turned out wrong | Changed course on evidence. Show what signal told you and how you reversed safely. |
| Disagreeing with an executive on direction | Brought data, framed the trade-off in business terms, and either persuaded or committed. |
| Getting teams to adopt a standard | Made the right way the easy way (templates, tooling, review) instead of mandating. |
| Simplifying an over-engineered system | Removed a component and measured the result. |
| Speed against compliance or security | Found a design that met the deadline without a compliance gap, or explicitly negotiated scope. |
| Growing other engineers as designers | Ran design reviews that taught, and can name someone who grew. |
Videos and reading
Every link here is also placed in the module where it's most useful. Watch with a purpose: pause and predict before the speaker explains.
Sources for the interview format
- ReadExponent · Capital One interview process
- ReadIGotAnOffer · Capital One software engineer interview
- ReadInterview Query · Capital One software engineer guide
- ReadPracHub · Capital One system design questions
- Readcodinginterview.com · Capital One system design interview questions
- ReadGlassdoor · Banking system supporting four transaction types
- ReadTaro · Principal Software Engineer, Toronto, November 2025
- ReadCapital One Canada · The job posting