•CASE STUDY

Virtual Credit Card Service (Capital One)

6 min read·1,141 words·Intermediate

Asked at

2 candidate reports between Dec 2025 and Feb 2026

How to use this case study

SDE-2 / Mid

  • Explain what a virtual card is
  • How it links to the real account
  • The rules checked when a transaction arrives (status, limit, merchant lock, expiry)

SDE-3 / Senior

  • Go deeper on the real-time authorization path and its latency budget
  • Tokenization
  • Decoding card IDs
  • Geography-aware rules

Staff / Principal

  • Discuss PCI scope
  • Fraud signals
  • High availability of authorization
  • The product trade-offs for users vs the bank

Problem RestatementProblem

A virtual card is a separate card number (with its own expiry and CVV) linked to a customer's real credit card account. Customers create them for online shopping: e.g., single-use cards, cards locked to one merchant (only Netflix can charge it), or cards with a spending limit. If a merchant is hacked, only the virtual number leaks, and the customer can close it without replacing their real card.

Capital One asked two parts: (1) product reasoning, meaning benefits and drawbacks for users and the bank, and (2) transaction validation: when a charge arrives with an encoded virtual card ID, decide approve or decline in real time. A related question added geography-aware rules.

Product Reasoning (say this briefly)

  • For users: safer online shopping, easy cancellation, spending control, and fewer real-card reissues.
  • Drawbacks for users: extra steps, and problems with merchants that need the physical card at pickup (hotels, rentals) or recurring charges tied to a single-use card.
  • For the bank: lower fraud losses and reissue costs, more engagement, and data on merchant subscriptions.
  • Costs for the bank: more complex authorization, more card numbers to manage, and support calls when a locked card is declined.

RequirementsRequirements

  • Create virtual cards with rules: single-use / multi-use, merchant lock (set on first use or chosen up front), spending limit (per transaction or monthly), expiry, and allowed countries.
  • Pause, resume or close a card.
  • Authorize each transaction in real time (under ~100 ms of our budget inside the card network's time limit).
  • Every decision is logged and explainable.

ArchitectureArchitecture

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    NET["Card network - auth request"] --> AUTH["Authorization Service"]
    AUTH --> VAULT["Token vault - decode virtual PAN"]
    AUTH --> VC[("Virtual cards + rules - cached")]
    AUTH --> ACC["Core account - available credit"]
    AUTH --> FR["Fraud scoring"]
    AUTH -->|"approve / decline + reason"| NET
    AUTH --> LOG[("Decision log")]
    APP["Customer app"] --> MGMT["Card Management API"]
    MGMT --> VC
    MGMT --> VAULT
  • Token vault: maps the virtual card number (PAN) to virtual_card_id and the real account. Card numbers live only here (a small PCI scope). The encoded ID in the request is decoded or looked up here.
  • Virtual card rules are cached in memory in the authorization service for speed, and invalidated on change.
  • Core account service: the real available credit, with holds placed on approval.

Validation Rules (in order, fail fast)

  1. Decode and look up the card. Unknown → decline "invalid card".
  2. Status: closed or paused → decline.
  3. Expiry and CVV match.
  4. Single-use: already used → decline.
  5. Merchant lock: if locked, the merchant ID must match. If "lock on first use" and not yet locked, lock it to this merchant atomically (so two first charges can't both lock).
  6. Limits: amount ≤ per-transaction limit, and monthly spent + amount ≤ monthly limit.
  7. Geography: the merchant's country is in the allowed list (and optionally matches the customer's recent location or travel notice).
  8. Real account: available credit ≥ amount → place a hold.
  9. Fraud score below a threshold, or step-up (send the customer a confirmation).
  10. Approve: record the decision, update counters (spent, used flag), and return the approval code.

Each decline returns a specific reason code for the network and the customer's app ("Declined: card locked to another merchant").

Deep Dive — Enforcing a virtual card's limits at authorisation timeDeep dive

A single-use card is presented, or a card locked to one merchant, or one with a $200 cap. The network wants an answer in well under a second, and the answer has to be right the first time.

Weak

Check the limit, then forward the authorisation

Read the card's rules and remaining balance, decide it is fine, pass the authorisation to the real account.

Sequence diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
sequenceDiagram
  participant M1 as Merchant A
  participant V as Virtual card service
  participant M2 as Merchant B
  M1->>V: authorise $150 - card limit $200
  V->>V: read remaining = 200, ok
  M2->>V: authorise $150 - same card
  V->>V: read remaining = 200, ok
  V->>V: both forwarded - $300 on a $200 card

The check and the spend are separate, so two authorisations arriving together both pass. A single-use card is worse: both charges succeed on a card that was supposed to work exactly once.

Good

Decrement conditionally as part of the decision

Make it one statement: UPDATE cards SET remaining = remaining - :amt WHERE card_id = :id AND remaining >= :amt. Zero rows means decline.

The race is gone and the arithmetic is right. What is wrong is the model: an authorisation is not a charge. The merchant may capture less than they authorised, capture days later, or never capture at all — a hotel holds $200 and charges $140. Decrementing at authorisation permanently consumes money that was never spent, and the customer's limit is wrong until somebody notices.

Best

Hold at authorisation, settle at capture, release on expiry

Model the two events separately, which is what the card networks actually do:

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  AUTH["Authorisation request"] --> RULES{"Merchant lock, amount cap, card active?"}
  RULES -->|"fails"| DECL["Decline - reason recorded"]
  RULES -->|"passes"| HOLD["Place a hold - atomic, counts against the limit"]
  HOLD --> FWD["Forward to the real account"]
  HOLD --> SU{"Single-use card?"}
  SU -->|"yes"| CLOSE["Close the card immediately - no second authorisation"]
  FWD --> CAP["Capture arrives - settle, convert the hold to a charge"]
  FWD --> REV["Reversal or expiry - release the hold"]
  CAP --> LESS["Captured less than held? Release the difference"]
  • Holds are atomic and count against the limit, so concurrent authorisations cannot both pass — but they are provisional, so unspent money comes back.
  • Expire holds on a timer. Authorisations that are never captured must release, or the card slowly fills with phantom spend. This is the sweeper the previous rung had no need for and no place to put.
  • A single-use card closes at the first successful authorisation, not at capture. Waiting for capture leaves a window in which a second authorisation is still valid, which is exactly what the card exists to prevent.

Run the validation rules in cheapest-first order and fail fast: card active, merchant matches the lock, amount within the per-transaction cap, then the balance check that requires a write. Most declines are decided before touching the expensive path.

Concurrency and Reliability

  • Two charges at the same time on a single-use card: use a conditional update (SET used = true WHERE id = ? AND used = false). Only one succeeds.
  • Monthly spend counters are updated atomically alongside the hold.
  • Idempotency: networks retry authorization messages, so decisions are keyed by the network's transaction ID, and a retry returns the same answer.
  • High availability: multi-region active-active authorization with replicated card rules. If the core account system is slow, card networks allow stand-in decisions with conservative limits.

Trade-offs & AlternativesTrade-offs

DecisionChoiceWhyAlternative
Card dataSeparate vault (tokenization)Small PCI scope, saferStore PANs everywhere: huge compliance risk
Rule checksIn-memory cached rules, fail fastMeets network latencyDB queries per check: slow
First-use lockAtomic conditional updateNo double locksRead-then-write: race
RetriesIdempotent by network transaction IDSame answer on retryNew decision each time: double holds

Wrap-UpWrap-up

Virtual cards give customers safer, controllable card numbers linked to one real account, with lower fraud costs for the bank. Authorize each transaction by decoding the card through a token vault, then checking cached rules in a fail-fast order: status, expiry, single-use, merchant lock, limits, geography, available credit and fraud. Use atomic conditional updates for single-use and first-use locks, idempotency by network transaction ID, and explainable decline reasons.

More Case Studies

Frequently Asked Questions

What is the Virtual Credit Card Service (Capital One) system design question?

Virtual Credit Card Service (Capital One) is a system design interview question asked at FAANG companies. It covers fintech, payments, security, real-time and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Virtual Credit Card Service (Capital One) question?

Capital One have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Virtual Credit Card Service (Capital One) interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Virtual Credit Card Service (Capital One) question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →