Design a Credit-Card Platform
- Model a card account where authorization only holds credit and settlement posts the balanced entry
- Make posting idempotent so a duplicate charge bills once, and keep available credit right under races
- Work the parts that decide the round: the authorization-to-settlement two-phase, ACID double-entry consistency, and pre-aggregated spend stats by period
Understanding the problem
Design the platform behind a Capital One credit card. Someone applies and gets an approval decision, signs in, then authorizes and posts card transactions. They can read a balance and statements and pay the bill. Spend rolls up daily, weekly, and monthly.
This platform has two lives. First it is an application funnel: someone applies, the bank runs identity and credit checks, and an approval decision opens an account with a credit limit. Then, for years after, it is a servicing system: every swipe authorizes against that account, clears overnight, posts to a balance, rolls up into a monthly statement, and takes a payment. The funnel is a one-time event per customer. The servicing is a firehose that never stops. The interview lives in the servicing half, because that is where money moves, and a wrong number there is a real person's money.
Name the hard part before drawing a box. A card transaction is two events, split across time. At the register the merchant asks "approve fifty dollars on this card?" and the bank places a hold. That night, in a batch file, the merchant submits the real amount to be settled, and only then does it post to what the cardholder owes. Three things have to be exactly right across that gap. A charge that arrives twice, from a network resend or a reprocessed batch, must post once. The balance must be ACID-correct, because it is money owed on a real account. And available credit has to account for holds that have not posted yet, or two purchases at once can push a card past its limit. Get those three right and the rest is plumbing.
Functional requirements
Scope hard, because the servicing loop is where the depth is and the marketing pages around it are not. The core is six things. Open an approved account, authorize a charge against available credit, and settle the cleared charge onto the balance. Take a payment, serve balance and statements, and roll spend up by period. Everything else you name and set aside.
In scope
- Apply for a card: identity and credit checks, then an approval decision that opens an account with a limit
- Authorize a charge: check available credit and place a hold, answered in tens of milliseconds
- Settle the cleared charge: post an immutable, balanced ledger entry for the real amount and release the hold
- Take a payment, and serve the balance, available credit, and monthly statements
- Roll posted spend into daily, weekly, and monthly totals per account and category
Below the line (call it out, then skip)
- Fraud and risk scoring on an authorization (a whole system of its own; we call the hook and move on)
- The card-network rails themselves (Visa and Mastercard clearing; we consume their files, we do not build them)
- Rewards, interest accrual, and late-fee assessment (posted as their own transactions on top of this ledger)
- Statement rendering, email delivery, and the mobile-app UI
- The KYC document vendors and the underwriting model internals (we call the decision, we do not train it)
Non-functional requirements
These are the requirements that pick the design, so put a target on each. Authorization is on a hard latency budget: the card network wants an approve-or-decline inside a couple of hundred milliseconds, because a customer is standing at a register. Posting to the ledger is correctness-first, not latency-first: it can take a beat, but it must be ACID and it must be idempotent. Available credit must be strongly consistent at authorization time, or concurrent charges overshoot the limit. Balance and statement reads are allowed to lag by a moment. And the whole ledger is append-only and kept for years, because financial records are audited.
Scale is large but the shape is friendly. A big issuer carries on the order of a hundred million active card accounts. The aggregate authorization rate lands in the low tens of thousands per second at peak, which no single component should struggle with once the work is spread. The threat is correctness under two kinds of contention. Two authorizations on the same card can read available credit at the same instant, and the same cleared transaction can arrive twice in a settlement batch. Hold every amount as an integer count of cents, never a float, because a ledger that rounds is a ledger that is wrong.
- Auth latency: ~200 ms budget. Approve or decline while the customer is at the register. Miss it and the network times the request out, so the authorization path is the one place latency is sacred.
- Posting: ACID + idempotent. A charge posts exactly once and atomically, or it does not post at all. Correctness beats speed here, because the number is money owed and a duplicate is a double bill.
- Available credit: Strongly consistent. Checked and reserved at auth time. Holds subtract from it alongside the posted balance, so two concurrent charges cannot slip a card past its limit.
- Ledger: Append-only, 7 to 10 yr. Never updated, never deleted; financial records are audited. Corrections post forward as reversing transactions, and cold partitions age out to object storage.
The set-up
Core entities
Name the objects money moves through before drawing anything. Four carry the design, and the split between the provisional ones and the permanent one is the whole trick.
Account
A cardholder's card account, identified by an id and owned by a customer, carrying a credit_limit and a status. Its balance, what the cardholder owes, is the sum of its posted ledger entries. Its available credit is the limit minus that balance minus the pending holds.
Authorization
A provisional hold placed at swipe time, identified by its own id. It keys off the account and records an amount in cents, the merchant it was placed for, and an expiry, along with a status (pending, settled, expired, reversed). It reduces available credit but never touches the posted balance. It is the first half of a transaction.
Ledger entry
The permanent record: an immutable, append-only line in integer cents, part of a balanced transaction whose entries sum to zero. Settlement and payments write these, and nothing ever updates or deletes one. This is the source of truth for the balance.
Statement
A billing-cycle rollup. It carries the opening balance, every transaction posted during the cycle, and the closing balance, then the minimum due and its due date. Derived from the ledger and frozen when the cycle closes.
Here is the line the whole design leans on. An authorization is provisional. It reserves credit so the customer cannot overspend, but it is not money owed yet, and it can expire or settle for a different amount than it held. A ledger entry is permanent. It exists only once a charge has actually cleared, it is written once, and it is never edited. Money is modeled double-entry: every posting is a balanced transaction whose entries sum to zero, so the books never create or destroy a cent, they only move it. A charge of 5000 cents that settles writes +5000 to what the cardholder owes and -5000 to a network-settlement account, and the two cancel. A mistake is fixed by posting a reversing transaction, not by rewriting the past, which is what keeps the ledger auditable. The transaction ledger lesson works the double-entry model all the way down.
The store under the ledger is Postgres, and the choice is load-bearing. Posting a charge has to write all its entries together and enforce a zero sum before anything is visible, which one relational transaction gives you: BEGIN, insert the entries, check the invariant, COMMIT. The rows are tiny and uniform, so the table grows enormous in count but stays trivial per row, and you partition it by account_id. Holds live somewhere different, in DynamoDB, because an authorization is a short-lived key-value item, read and written by account at very high rates and expired on a TTL. That is a key-value workload with a hard latency floor, not a relational one. The PostgreSQL lesson covers the transactional guarantees underneath the ledger choice.
API
The interface is small, and it makes the two-phase explicit. A charge is authorized, then later settled, on two separate calls, and both carry an idempotency key so a retry is safe. Balance and spend are plain reads. Amounts are integer cents on the wire and in storage.
Two calls carry the round. Authorizing and settling are separate on purpose. They happen at different times and the amount can change between them, so the API models the hold and the posting as distinct events rather than pretending a charge is atomic. And every write takes an Idempotency-Key, because each of these requests can be retried or redelivered by systems outside your control, and a bank cannot afford to act on the same charge twice.
High-level design
Walk one charge through the platform, and build the simplest correct version before hardening it. The diagram colors each box by role. The cardholder and merchant and the card network are blue. The services, the API gateway, Authorization service, Ledger service, and Settlement worker, are violet. Kafka is amber. The stores, Postgres, DynamoDB, and the spend rollups, are green.
1) Apply, and open an account. The cardholder applies through the API gateway, which routes to the Application service. It runs identity verification and a credit decision, and on approval opens a Postgres account with a credit limit. This is the one-time funnel, and it is deliberately the light half of the design. It runs once per customer, tolerates seconds of latency, and calls out to KYC and underwriting vendors you treat as black boxes. Everything demanding comes after the account exists.
2) Authorize a charge, and hold the credit. When the customer pays, the merchant's request reaches the Authorization service through the gateway. It has a hard job on a tight clock: inside a couple of hundred milliseconds, confirm the card is open and not flagged, check that available credit covers the amount, and place a hold. The hold lands in DynamoDB, keyed by account, and it drops available credit without posting anything to the balance, because the money has not moved yet. Nothing is owed on a hold. Two authorizations racing on the same card is where this gets subtle, and that reservation is deep dive three.
3) Settle the cleared charge onto the ledger. The charge does not post when it is authorized. It posts that night, when the card network delivers a clearing file of the transactions that actually went through and the Settlement worker reads it. For each cleared charge the Ledger service posts a balanced double-entry to the Postgres ledger for the real cleared amount, then releases the matching hold. The cleared amount can differ from what was authorized, a tip added at a restaurant, a final total at the pump, so settlement is where the true number lands. A clearing record that arrives twice must still post once, which is deep dive one.
4) Serve balances and statements, plus the spend view. A read of balance and available credit comes straight off the Postgres account, and a monthly statement is a frozen rollup of one billing cycle. Spend stats are the interesting read. Posted movements stream out of Postgres through Kafka and fold into spend rollups keyed by account and period, so nothing scans the ledger per request. A "this month" total is a point read. Keeping those rollups fresh and correct as late settlements land is deep dive five.
Size the platform
Work the arithmetic, because it sets the latency and storage story. Take a large issuer at roughly a hundred million active card accounts. A single account averages on the order of two authorizations a day once you blend heavy spenders against dormant cards, so the portfolio sees about two hundred million authorizations a day. Divide two hundred million by 86,400 seconds and that is close to 2,300 authorizations a second on average. Peaks run several times higher, a holiday weekend or the dinner rush, so size the authorization path for something like 10,000 a second. That is the number the tight latency budget has to hold up against.
Now the ledger. Almost every authorization eventually clears, so roughly two hundred million settlements post a day. Each is a balanced transaction of at least two entries, so the ledger absorbs on the order of four hundred million entries a day. At about 250 bytes an entry with indexes (a handful of integers and a currency code), the log grows by about 100 GB a day, on the order of 35 TB a year. That figure only climbs, because nothing in the ledger is ever deleted. Records stay queryable for seven to ten years for audit, so the design partitions the ledger by account and ages cold partitions out to cheap object storage rather than truncating anything.
- Active accounts: ~100M. A large US issuer. This is the base every other number scales from, so state the assumption and let the interviewer adjust it.
- Authorizations: ~200M/day. 100M accounts times about 2 a day is ~200M, which over 86,400 s is ~2,300/s average. Peaks a few times higher, so size the auth path for ~10K/s.
- Ledger writes: ~400M entries/day. ~200M settlements clear a day at 2+ entries each. At ~250 bytes a row with indexes that is ~100 GB/day of append-only writes.
- Storage: ~35 TB/yr, kept 7 to 10 yr. Append-only and never deleted. Partition by account, keep hot cycles in Postgres, and age cold partitions to object storage that stays queryable.
Technology choices
Each box is a real category of tool. Name a concrete pick, say why that kind of tool fits a card platform, and have the failure question ready, because a correctness round pushes on every store you put on the board.
- Postgres holds the ledger and the accounts, and the choice is load-bearing. Posting a charge needs all the entries of one transaction to commit together and to sum to zero before anything is visible, which is what a single relational transaction gives you directly. The rows are tiny and uniform, so the table is enormous in count but trivial per row, and you partition it by
account_idas it grows. The failure probe is a torn write, and nothing is owed until the one database transaction commits, so a crashed poster leaves no half-written charge. The retry carries the same idempotency key and lands it exactly once. An eventually-consistent store cannot promise a balanced atomic post, and that guarantee is the one a ledger cannot live without. - DynamoDB holds the authorization holds, and this split is the one most people miss. A hold is a short-lived key-value item, written and read by account id at very high rates and expired on a TTL when an auth is never cleared. That is a key-value workload with a hard latency floor, not a relational one, and a managed key-value store serves the single-digit-millisecond read the authorization budget depends on. The probe is consistency: available credit must not be read stale while a charge races, so the reservation uses a conditional write, a strongly-consistent atomic decrement, which deep dive three works through.
- Kafka carries posted movements off the write path, fed by change-data-capture from the Postgres ledger. The ledger is already an append-only log in shape, so publishing each committed entry as a stream fits naturally. Spend rollups, statement generation and downstream analytics jobs all read the stream instead of querying the transactional database. The probe is the dual-write trap, where a service writes Postgres and Kafka separately and one fails. Deriving the stream from the committed log through change-data-capture fixes it. A movement is then published only because it was already durably posted.
- The authorization and ledger services are stateless app tiers behind the API gateway, which terminates TLS, authenticates the caller, and rate-limits. The services stay stateless. Every invariant, from the credit check to the atomic post to the idempotency key, is enforced in the datastore, so any instance serves any request and you scale by adding boxes. The gateway is also where login and session auth live, so the services behind it trust the caller identity on every call rather than re-checking it.
- S3 plays two roles a relational database should not. The card networks' nightly clearing and settlement files land there for the Settlement worker to stream through, and S3 also holds cold ledger partitions aged out of Postgres. That keeps the seven-to-ten-year retention from inflating the hot tier. Object storage is built for large write-once files read occasionally, which is this workload exactly. Its eleven-nines durability with versioning means an archived partition or a settlement file cannot be silently lost, and an audit trail needs that.
Potential deep dives
The deep dives carry most of the grading. The interviewer fixes on one property of the platform and presses until it bends, because correctness under real money is what this round exists to test. The five below are the ones Capital One reaches for, each scored against the tiers an interviewer grades you on.
1) A charge arrives twice. How does it post once?
Start with why a duplicate is the default, not the exception. A clearing file can be redelivered after a network timeout. A batch job can be reprocessed after a crash. A merchant can resubmit. So the same cleared charge reaches the settlement path more than once as a matter of course, and if each arrival posts a ledger entry, the cardholder is billed twice for one purchase. The fix is an idempotency key: a stable identifier for the charge, carried on the settlement request, that the ledger uses to recognize a charge it has already posted. Work up to it in tiers.
Approach
Read each cleared charge from the file and post its ledger entry, trusting that each charge appears exactly once.
Challenges
A single redelivered file or reprocessed batch double-posts every charge in it, and the cardholder is billed twice with a real ledger entry behind each. There is no key to notice the repeat, so the duplicate looks exactly like a second genuine purchase. In a bank that is not a glitch: it is a wrong bill, it triggers a support call, and it leaves a paper trail. Name it only to motivate a key.
Approach
Give each charge an idempotency key (the network's transaction id works), and before posting, SELECT whether that key already exists. If it does, skip. If it does not, post the entry and record the key.
Challenges
The check and the insert are two steps, and two copies of the same charge can race between them: both SELECT and find nothing. Both then post. The charge lands twice. Under a batch reprocessed in parallel this is not hypothetical, it is the common case. The gap between checking and acting is the bug, so recognizing the duplicate has to be atomic with the write, not a separate look-first step.
Approach
Put a unique constraint on the idempotency key in the same database transaction that writes the ledger entry. The post becomes BEGIN, insert the entry tagged with the key, COMMIT. A second insert of the same key hits a uniqueness violation, which the poster catches and treats as success. Now recognizing the duplicate and posting the charge are one atomic act, so a redelivery or a reprocess, and even two racing copies, all collapse to exactly one entry. Derive the key from the charge's own network id rather than generating a fresh one per attempt, so it is identical across redeliveries.
Challenges
The key must be durable and identical on every retry, and scoped correctly, per charge and not per file, or a legitimately repeated small purchase gets swallowed as a duplicate. The poster has to treat the uniqueness violation as success and return the original result, not as an error to retry, or a redelivery turns into a failed batch. Within one account this is airtight. The rare cross-system disagreement is what reconciliation against the network's settlement totals is there to catch.
The handling contention lesson covers unique constraints and atomic writes in general, and the transaction ledger lesson carries the same idempotency key through the double-entry model.
2) The two-phase charge: authorization holds, settlement posts
This is the piece generic prep misses, so it is where Capital One spends its follow-ups. A card charge is two events split across time. At the register, an authorization asks the issuer to approve an amount and reserve it. Hours or a day later, clearing tells the issuer the amount that actually went through, and settlement posts it. The gap matters because the two amounts can differ, and because the hold has to protect the credit limit in the meantime. Model it as one event and the design breaks the first time a restaurant tip or a pay-at-the-pump total lands.
Authorization (the hold)
- Placed at swipe time, answered in ~200 ms while the customer waits
- Reserves credit: available credit drops, the posted balance does not move
- Provisional and reversible; expires on a TTL, about a week, if the charge never clears
- Lives in DynamoDB as a short-lived item, not in the permanent ledger
Settlement (the post)
- Driven by the network's nightly clearing file, off the latency path
- Posts an immutable balanced entry for the cleared amount; the balance finally moves
- Permanent; a correction is a new reversing transaction, never an edit
- Releases the matching hold, so available credit reflects the real posted amount
The lifecycle in full. An approved authorization writes a hold and reserves credit. When the clearing record arrives, settlement posts the real amount to the ledger and releases the hold, so available credit swings from "reduced by the estimate" to "reduced by the actual." Three cases test whether you actually understand it. The cleared amount comes in higher than the auth, a tip: you post the cleared amount and release the full hold, and the design allows for the difference instead of assuming they match. The charge never clears: the hold expires on its TTL and the reserved credit comes back on its own, with nothing ever posted. The merchant sends an incremental auth, a hotel extending a stay: you add a second hold rather than editing the first. The honest version keeps holds and posted entries as separate records, so available credit is always the credit limit minus the posted balance minus the live holds.
3) Two charges race on one card. How does the limit hold?
The ledger's ACID posting keeps the balance correct once a charge settles, but the sharp concurrency problem is earlier, at authorization. Two purchases on the same card can hit the authorization service at the same instant, a card and its authorized-user copy, or an online and an in-store charge together. Each checks available credit, each sees enough room, and if the check and the reservation are not atomic, both approve even though only one fits under the limit. Now the card is authorized past its credit line. Size it as a read-modify-write race: read available credit, decide, write the new hold, with a window in between where the other charge slips through.
Approach
SELECT the account's available credit, check in the service that it covers the amount, and if so insert a hold and decrement the number.
Challenges
Two concurrent auths both read the same available credit before either writes its hold, both pass the check, and both post holds, so the account is reserved past its limit. The window between the read and the write is small, but it is hit constantly at portfolio scale, and the failure is the expensive direction: letting a customer overspend. Checking in application code cannot fix a race the database is the one arbitrating.
Approach
Make the reservation one atomic conditional write: decrement available credit only if it is still at least the amount, in a single statement the datastore serializes per account. That is a conditional UPDATE ... WHERE available_credit_cents >= :amt, or a DynamoDB conditional update on the account item. If the condition fails, the charge is declined. Now the check and the reservation are inseparable, so of two racing auths exactly one wins the decrement and the other is cleanly declined.
Challenges
This serializes writes on the same account. That is correct, and for a single card it is no real bottleneck. Even the busiest consumer card sees a handful of concurrent auths, so per-account serialization costs nothing here. A hot merchant would be a different story. The subtlety is that the reserved credit now lives in the hold, so settlement has to release exactly the right hold to give it back, which ties this dive straight to the two-phase one.
Approach
Treat the hold as the reservation and the ledger as the truth, and keep them consistent. The auth does the atomic conditional decrement to place the hold, so the limit is enforced the instant credit is reserved. Settlement posts the cleared amount to the ACID ledger and releases the hold in the same motion, so available credit recomputes from a consistent pair: limit minus posted balance minus live holds. The ledger is the authoritative double-entry record, so a periodic reconciliation recomputes each account's balance from its entries. That catches any drift between the fast available-credit number and the posted truth, and a reversing transaction corrects it instead of an edit.
Challenges
The two stores, the holds in DynamoDB and the ledger in Postgres, have to agree, so releases must be idempotent and a crashed settlement must be retryable without double-releasing a hold. Reconciliation is the backstop for the rare disagreement no single atomic write can prevent. The payoff is a limit that holds under concurrent charges and a balance that stays provably correct against its own entries.
The handling contention lesson generalizes the atomic conditional-write pattern behind the reservation. It is the same instinct as any reserve-then-confirm: make the check and the decrement one indivisible step so no two callers can both win it.
4) Reconciliation: fixing a mistake without editing the past
The atomic post keeps each charge correct in the moment, but a ledger has to stay honest over time too, and two questions decide whether it does. How does a wrong entry get corrected once it is already written? And how does the ledger stay in agreement with the outside world that actually holds the money? Neither is about throughput. Both are about correctness that survives.
Corrections go forward, never backward. Because entries are immutable, a wrong 5000-cent post is undone by posting a new transaction that moves 5000 cents back, a reversing entry, which leaves both the mistake and its correction on the record. The balance nets the two and lands correct, and since nothing was erased the history stays complete. An auditor can trace every cent of a balance to the movements that produced it, the errors and their fixes included. That is exactly why an UPDATE or DELETE of a posted entry is not an operation the ledger offers. The moment a process can quietly rewrite a past number, the audit trail is worthless.
Reconciliation is the second layer, and the strong answers raise it unprompted. The ledger records what Capital One believes cleared, while the card network's settlement files are the authoritative record of what money actually moved. A batch job off the request path reads those files and compares them line by line against the ledger, flagging any disagreement. That might be a charge the ledger posted that the network later reversed, or a settlement the network reports that the ledger never recorded. A confirmed discrepancy is corrected the only way the ledger allows, with a reversing or adjusting transaction. The idempotent atomic posts keep each movement right as it happens, and reconciliation catches the rare drift no single write can.
5) Spend by period, without scanning the ledger
The stats feature looks innocent and is a genuine scaling question. A customer opens the app and wants this month's spend, last week's, today's, often broken down by category. The naive read is a SUM over the account's ledger entries for the period, computed fresh each time. That is correct, and it does not scale. A long-lived account holds years of entries, and summing a window on every app open across a hundred million accounts is a mountain of repeated work for numbers that barely change. Pre-aggregate instead: do the summing once as charges post, and serve a read as a lookup.
Sum the ledger on read
- Spend is a
SUMover the account's entries in the period, computed per request - Nothing extra to maintain, so the write path stays simple and always correct
- Read cost grows with account age and gets slow for long-lived, heavy spenders
- Repeats the same summation on every app open for a number that rarely changed
Pre-aggregate into period rollups
- A rollup row per (account, period, category), updated as each charge posts
- A "this month" read is a point lookup, constant time regardless of history
- Costs maintained rollups and a job to keep them right as late settlements land
- The read the app actually makes becomes cheap; the work moves to write time
The practical build streams posted movements out of the ledger through Kafka and folds each into its rollups. When a charge settles, its amount folds into that account's buckets at each period grain (day, week, month) and into its category bucket, keyed by (account, period). Reading spend is then a point read of a handful of small rows. Settlement, not authorization, drives the rollup, because only settled charges are real spend, and a hold that expires never should have counted. A charge posts on the clearing date, sometimes a day or two after the swipe, so the fold keys on the transaction's own date and a late settlement lands in its true period. A reversal is just a negative amount folded the same way, so a refund decrements the bucket.
The scaling reads lesson covers maintaining derived read models off an authoritative log. A spend rollup is one: a materialized view of the ledger that the write path keeps warm so the read path stays cheap.
What is expected at each level
That is a wide surface for one platform. What you have to land depends on the level, and this design is deliberately level-gated: lighter for the Tech Development Program and new grads, heavier from Senior up.
Associate and mid-level
Breadth carries it. Get to a correct servicing loop. Model the account, place a hold at authorization, and post an immutable balanced entry at settlement, holding money in integer cents. Explain why a charge that arrives twice must post once, and reach for an idempotency key. The concurrency and stats cases are the interviewer's to raise. The bar: you can walk a charge from swipe to posted balance, and you know that authorization and settlement are separate steps rather than one.
Senior
Cover the breadth, then own the two-phase and the concurrency. You are expected to model holds and posted entries as separate records without prompting, and to derive available credit as limit minus balance minus holds. Find the read-modify-write race on the credit limit yourself and fix it with an atomic conditional reservation. Make posting idempotent with a unique constraint in the write transaction, and handle the amount changing between auth and settlement. The bar: you keep the money correct under duplicates and concurrent charges, and you justify each store as a deliberate choice rather than a default.
Lead and above
Depth across the hard parts and a widening scope. Lead on the ledger's ACID double-entry and reconciliation against the network's settlement totals. Then cover the consistency split between the fast available-credit number and the authoritative entries. Add partitioning and retention over the seven-to-ten-year window, and the streaming rollups that keep spend stats cheap and correct as late settlements land. You treat every correction as a reversing transaction on principle, and you drive the design while the interviewer redirects rather than rescues. The bar: idempotency and the two-phase and ledger correctness all stay at the center at once, and you reason about money end to end.
Key Takeaways
- ✓A card charge is two events, not one. An authorization holds credit in a fast key-value store without touching the balance; settlement posts an immutable, balanced double-entry to the ledger for the actual cleared amount and releases the hold. Model them separately, or the design breaks the first time the settled amount differs from the authorized one.
- ✓Posting must be idempotent, because a charge arrives twice as a matter of course. Tag each charge with a stable idempotency key from the network transaction id, under a unique constraint inside the posting transaction. Recognizing a duplicate and writing the entry become one atomic act, so a redelivered clearing record posts once.
- ✓Keep money correct under contention. Available credit is derived as limit minus posted balance minus live holds, and two charges racing on one card are resolved by an atomic conditional reservation so exactly one wins. The ledger is ACID double-entry in integer cents, and every correction posts forward as a reversing transaction rather than editing the past.
- ✓Serve spend stats from pre-aggregated period rollups, not a scan of the ledger. Stream posted movements through Kafka and fold each into (account, period, category) buckets keyed on the transaction date. A monthly total becomes a point read, and a late settlement still lands in the right period.
Capital One interview guideKnow what Capital One asks before you walk in.
5 coding · 2 system design · 30 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.