Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.
65,000 Messages Per Second — Capacity Built for the Busiest Day of the Year
Imagine building a system where a farmer in rural Nebraska can swipe a card at 2 AM, and within 2 seconds, a bank in Tokyo has approved the transaction, a merchant in Dubai has been guaranteed payment, and fraud detection AI has analyzed 500+ risk attributes — all while maintaining 99.9999% uptime.
Now imagine building it to handle 65,000 of those messages. Per second. Across 200+ countries. In 160+ currencies. With $0 tolerance for double-charging.
This is VisaNet and Mastercard's payment network — the largest real-time financial system ever built, processing roughly $27 trillion a year (2025) and touching a large share of the world's card transactions.
Volume
- Visa-brand Transactions: ~329 Billion/year (FY2025, payments + cash; 258B of them processed on VisaNet)
- Mastercard-brand Transactions: ~222 Billion/year (2025, purchase + cash; 175.5B switched by Mastercard)
- Network-processed Daily Volume: ~1.2 Billion transactions/day (258B + 175.5B ≈ 434B/year)
- Peak Capacity: 65,000+ messages/second (Visa's stress-tested figure; Visa says its network processes "up to 83,000 transaction messages per second", Oct 2025)
Scale
- Visa Cards: 4.9 Billion credentials globally (FY2025)
- Mastercard Cards: 3.7 Billion cards worldwide (Mastercard + Maestro, end-2025)
- Merchant Locations: 175+ Million (Visa, FY2025)
- Countries: 200+ countries and territories (Visa, FY2025)
- Currencies: 160+ supported (Visa)
Performance
- Authorization Latency: < 2 seconds end-to-end (design target)
- Fraud Scoring: ~1 millisecond for 500+ risk attributes (Visa Advanced Authorization, 2019)
- Network Uptime: 99.9999% (six 9s, Visa's stated figure, Oct 2025)
- Fraud Prevented (Visa): $40+ Billion/year (Visa, Oct 2025)
Financial
- Visa Total Volume: $16.7 Trillion/year (FY2025, incl. cash)
- Combined Volume: ~$27 Trillion/year (adding Mastercard's $10.6T 2025 GDV)
- Visa Revenue: $40.0 Billion (FY2025, year to Sept 2025)
- Mastercard Revenue: $32.8 Billion (2025)
Infrastructure
- Visa Data Centers: 7 independent data centers (Visa, Oct 2025; its FY2025 10-K counts four global data centers)
- Security Events/Day: 22 Billion monitored (Visa, Oct 2025)
- AI Risk Attributes: 500+ analyzed per transaction (Visa Advanced Authorization, 2019)
This is the system we'll design today. Visa and Mastercard publish their scale, not their internals: the components, technologies, timeouts and thresholds from Phase 3 onward are a likely design for a network like theirs, not either company's actual system.
Card networks process ~434 billion transactions a year between ~14,500 banks: ~13,800 TPS on average, ~55,000 on Black Friday, built for ~100,000. A purchase goes POS, acquirer, network (BIN routing plus a fraud score in under 1 ms) and issuer, and money only moves at clearing and settlement, T+1 to T+2. The deep dives cover the in-memory 200 ms path, fraud scoring, tiered timeouts and stand-in on issuer rules, six-nines data centres and interchange, then 10x scale, idempotent retries, graceful degradation and catching card-testing bots.
The Interview Begins#
You walk into the interview room. The interviewer smiles and gestures to the whiteboard.
"Thanks for coming in. Today we're going to design a global card payment network — something like Visa or Mastercard. I want to understand how you'd architect a system that can authorize a payment in under 2 seconds, anywhere in the world, while preventing billions in fraud. Ready?"
They write on the whiteboard:
A credit/debit card network connecting cardholders, merchants, issuing banks, and acquiring banks globally.
Requirements
- Process 50,000+ transactions per second peak
- Authorization response in < 2 seconds
- Real-time fraud detection with < 0.1% false positive rate
- Support 200+ countries and 160+ currencies
- 99.999%+ availability (five 9s minimum)
- Never double-charge, never lose transactions
"Take a few minutes to think about this, then walk me through your approach. We have about 45 minutes."
Phase 1: Requirements Clarification (5 minutes)#
Before diving in, you take a breath and start asking questions. This is crucial — never assume.
Your Questions#
"Before I start designing, I'd like to clarify a few requirements. First, can you describe the basic flow? Who are the participants in a card transaction?"
"Great question. It's a four-party model: the cardholder who swipes their card, the merchant who accepts the payment, the acquiring bank that works with the merchant, and the issuing bank that issued the card to the cardholder. Our network sits in the middle, routing messages between the acquirer and issuer."
"So the network doesn't hold funds or issue cards — it's purely a message routing and processing layer?"
"Exactly. We route authorization requests, handle clearing and settlement, and provide value-added services like fraud detection. The actual money movement happens between banks."
"What's the latency budget for authorization? And how is it distributed?"
"End-to-end from card swipe to response should be under 2 seconds. But the merchant terminal, acquirer systems, and issuer systems all consume time. Our network portion should be well under 200 milliseconds — ideally much faster."
"For fraud detection, how much latency can we add to the critical path?"
"It must be inline with authorization — no more than 1-2 milliseconds. We can't slow down legitimate transactions."
"What happens if the issuing bank is unreachable during authorization?"
"Good question. We need 'stand-in processing' — the ability to make authorization decisions on behalf of an unreachable issuer within pre-agreed parameters."
"One more — what's the settlement timeline?"
"Clearing happens within hours of the transaction. Settlement — the actual money movement — typically happens T+1 to T+2 through banking systems. But that's separate from the real-time authorization path."
Functional Requirements#
1. Authorization (Real-time)
- Route authorization requests from acquirer to issuer
- Return approve/decline within 200ms network time
- Support stand-in processing when issuer unreachable
- Handle multiple card types: credit, debit, prepaid
2. Clearing (Near real-time)
- Collect transaction details from acquirers
- Calculate interchange fees per transaction
- Prepare settlement positions for all parties
- Handle adjustments, chargebacks, reversals
3. Settlement (Batch)
- Calculate net positions between all banks
- Facilitate fund transfers between banks
- Generate settlement reports and reconciliation
4. Fraud Detection (Real-time)
- Score every transaction for fraud risk
- Provide risk score to issuer for decision
- Detect patterns: velocity, geography, merchant type
- Block enumeration attacks (card testing)
5. Value-Added Services
- Tokenization for secure card storage
- 3D Secure for online authentication
- Currency conversion
- Loyalty and rewards integration
Non-Functional Requirements#
1. Scale
- 65,000 transactions per second peak capacity
- ~434 billion network-processed transactions per year
- 200+ countries, 160+ currencies
- Millions of connected endpoints (banks, processors)
2. Latency
- Authorization: < 200ms network portion (p99)
- Fraud scoring: < 1ms per transaction
- End-to-end: < 2 seconds including all parties
3. Availability
- 99.9999% uptime (32 seconds downtime/year max)
- Zero data loss for financial transactions
- Geographic redundancy across continents
4. Consistency
- Exactly-once processing for authorizations
- No double-charging under any failure scenario
- Full audit trail for every transaction
5. Security
- PCI-DSS Level 1 compliance
- End-to-end encryption
- Hardware security modules for keys
- Real-time threat detection
Phase 2: Back of the Envelope Estimation (5 minutes)#
"Let me work through the numbers to understand the scale we're dealing with."
Before the numbers: from a year to a second
VisaNet and Mastercard's network together process about 434 billion transactions a year, and Black Friday runs about four times the average rate. What is the average TPS, the peak, and the capacity you would actually build?
One strong answer
- Per day: 434B ÷ 365 ≈ 1.19 billion.
- Average: 1.19B ÷ 86,400 s ≈ 13,800 TPS.
- Peak: × 4 ≈ 55,000 TPS.
- Build for ~100,000 TPS, so a record day still has headroom.
The messages are tiny (a few hundred bytes), so bandwidth is only around a gigabit per data centre. The hard parts are latency, availability and correctness, not volume.
Transaction Volume Estimation#
- Base numbers (2025 actual, transactions processed on each network):
- Visa (VisaNet, FY2025): 258 billion
- Mastercard (switched, 2025): 175.5 billion
- Combined: ~434 billion/year
- Daily calculation:
- 434B / 365 days = ~1.19 billion/day
- Per hour average: ~50 million/hour
- Per second average: ~13,800/second
- Peak calculation:
- Peak events: Black Friday, Cyber Monday, Singles Day
- Peak multiplier: ~4x average
- Peak TPS: ~55,000/second
- Design capacity (headroom): ~100,000/second
Message Size Estimation#
- Authorization request fields:
- Message Type Indicator: 4 bytes
- Bitmap: 16 bytes
- Primary Account Number (PAN): 19 bytes
- Processing Code: 6 bytes
- Transaction Amount: 12 bytes
- Transmission DateTime: 10 bytes
- Merchant Category Code: 4 bytes
- Acquiring Institution ID: 11 bytes
- Card Acceptor Terminal ID: 8 bytes
- Additional data fields: ~200 bytes
- Typical message size: 300-500 bytes
- With encryption overhead: ~600 bytes
- Bandwidth calculation:
- 100K TPS × 600 bytes × 2 (req+resp) = ~120 MB/second
- = ~1 Gbps per data center
- With replication (3x): ~3 Gbps network capacity
Storage Estimation#
- Per transaction stored:
- Authorization record: ~1 KB
- Clearing record: ~2 KB
- Settlement details: ~500 bytes
- Total per transaction: ~3.5 KB
- Daily storage: 1.19B transactions × 3.5 KB = ~4.2 TB/day
- Annual storage: 4.2 TB × 365 = ~1.5 PB/year
- With 7-year retention: ~10.5 PB
- With replication (3x): ~32 PB total storage
Key Metrics Summary#
Traffic
- Peak transactions: 100,000 /second (design capacity)
- Daily transactions: 1.19 billion
- Annual transactions: 434 billion
Storage
- Per day: 4.2 TB
- Per year: 1.5 PB
- 7-year retention: ~32 PB (with replication)
Bandwidth
- Per data center: ~1 Gbps sustained
- Peak burst: ~5 Gbps
Infrastructure (rough)
- Data centers: 4-7 globally synchronized
- Connected banks: ~14,500 financial institutions (Visa, FY2025)
- Network coverage: 10+ million route miles of private telecom (Visa, 2017)
Requirements and estimation
Q1/3 In the four-party model, what does the card network itself do during an authorization?
Answer & explanation
Answer: A
The network is a message routing and processing layer: it doesn't hold funds or issue cards. The issuer makes the approve/decline call, and real money moves between banks later during settlement.
Q2/3 Combined volume is ~434B transactions/year and peak traffic is about 4x the average. Roughly what peak TPS should you plan for before adding headroom?
Answer & explanation
Answer: C
434B / 365 is ~1.19B/day, or ~13,800/second on average. A 4x peak multiplier gives ~55,000 TPS; ~13,800 is only the average, and the design rounds up to ~100,000 for headroom.
Q3/3 Why can settlement run as a T+1/T+2 batch when authorization must answer in under 200ms of network time?
Answer & explanation
Answer: D
The cardholder is standing at the terminal waiting for the approve/decline, so authorization sits on the latency-critical path. Settlement is the actual money movement between banks. It happens later through banking systems and nobody waits on it in real time.
Phase 3: High-Level Design (10 minutes)#
"Now let me sketch out the high-level architecture. The key insight is that this is a four-party model with the payment network in the center."
Sketch it first: the four parties
Name the four parties in a card payment and draw the path an authorization request takes and the path the answer takes back. Which party holds the cardholder's money, and does the network ever hold funds?
One strong answer
- The path: cardholder → merchant's terminal → acquirer (the merchant's bank) → network → issuer (the cardholder's bank), and the approve or decline travels the same way back.
- The issuer holds the account and makes the decision.
- The network routes by the card's BIN to the right issuer, adds a fraud score, stands in when the issuer can't answer, and later runs clearing and settlement.
- No funds in the network: authorization moves no money. Banks settle their net positions afterwards, typically T+1 to T+2.
The Four-Party Model#
%% title: The four-party model
flowchart TD
subgraph CHS["Cardholder"]
CON@{ icon: "sd:user", label: "Consumer" }
end
subgraph MS["Merchant"]
RET@{ icon: "sd:store", label: "Retailer" }
end
CON -->|"1. Swipes/Taps card"| RET
CON -->|"Has card from"| ISS@{ icon: "sd:bank", label: "ISSUING BANK" }
RET -->|"Has account with"| ACQ@{ icon: "sd:bank", label: "ACQUIRING BANK" }
ACQ ==>|"2. Auth Request"| NET["PAYMENT NETWORK<br/>(Visa/Mastercard)"]:::core
ISS -->|"3. Auth Response"| NET
ACQ -.->|"4. Settlement ($$$)"| ISS
class CON,RET client
class ISS,ACQ serviceSystem Architecture#
%% title: High-level architecture
flowchart TD
subgraph AS["Acquiring side"]
POS@{ icon: "sd:card", label: "POS Terminal" } --> AP["Acquirer Processor"]:::service
AP --> AGW["Acquirer Gateway"]:::edge
end
subgraph NC["Payment network core"]
MG["Message Gateway<br/>(ISO 8583 Protocol Handler)"]:::edge
RS["Router/Switch"]:::hot
FD["Fraud Detection"]:::core
SIP["Stand-In Processing"]:::core
AUTH["Authorization Processing<br/>(Transaction Validation, Enrichment)"]:::core
MG --> RS & FD & SIP
RS ==> AUTH
FD --> AUTH
subgraph DL["Data Layer"]
direction TB
PG@{ icon: "tech:postgresql", label: "PostgreSQL (Records)" }
RD@{ icon: "tech:redis", label: "Redis (Cache)" }
KF@{ icon: "tech:kafka", label: "Kafka (Events)" }
AN@{ icon: "sd:chart", label: "Analytics (OLAP)" }
end
AUTH --> DL
end
AGW ==> MG
subgraph IS["Issuing side"]
IG["Issuer Gateway"]:::edge --> IP["Issuer Processor"]:::service
IP --> CB@{ icon: "sd:bank", label: "Core Banking" }
end
NC ==> IG
class POS client
class PG,AN store
class RD cache
class KF queue
class CB serviceComponent Breakdown#
"Let me walk through each major component..."
1. Message Gateway#
Purpose: Protocol handling for ISO 8583 messages from connected banks and processors.
Key responsibilities:
- Parse and validate incoming ISO 8583 messages
- Handle multiple message versions and network variations
- Encrypt/decrypt sensitive data fields
- Route to appropriate internal services
Technology choice: Custom high-performance message parser, likely C++ or specialized hardware for latency-critical path.
2. Router/Switch#
Purpose: Route authorization requests to the correct issuing bank based on card BIN (Bank Identification Number).
Key responsibilities:
- BIN lookup to identify issuing bank
- Select optimal route (primary, fallback)
- Load balance across issuer connections
- Detect and route around failures
3. Fraud Detection Engine#
Purpose: Score every transaction for fraud risk in real-time.
Key responsibilities:
- Analyze 500+ risk attributes per transaction
- Return risk score within 1 millisecond
- Feed scores to issuer for decision support
- Detect velocity patterns, geographic anomalies
4. Stand-In Processing#
Purpose: Make authorization decisions when issuer is unreachable.
Key responsibilities:
- Maintain issuer-defined rules and limits
- Track card-level velocity and spending
- Approve/decline within pre-agreed parameters
- Queue transactions for later issuer reconciliation
5. Clearing & Settlement Engine#
Purpose: Process non-real-time clearing and facilitate settlement.
Key responsibilities:
- Collect and validate clearing records
- Calculate interchange fees per transaction
- Net settlement positions across banks
- Generate settlement files and reports
Data Flow#
"Let me trace through a typical authorization flow..."
%% title: Authorization flow (< 2 seconds end-to-end) · steps 1–3 sequenceDiagram autonumber participant CH as Cardholder participant POS as POS Terminal participant AP as Acquirer Processor participant MG as Message Gateway participant R as Router participant FE as Fraud Engine CH->>POS: Card presented POS->>AP: ISO 8583 message Note over CH,AP: Step 1 · Card Presented (0-200ms)<br/>Card data captured · PIN/CVV validated locally<br/>ISO 8583 message constructed AP->>MG: Acquirer to network MG->>R: validated message Note over AP,R: Step 2 · Acquirer to Network (200-400ms)<br/>Message validated and parsed · BIN lookup to identify issuer<br/>Transaction enriched (merchant data) R->>FE: score transaction FE-->>R: risk score Note over R,FE: Step 3 · Fraud Scoring (400-401ms)<br/>500+ attributes analyzed · Risk score calculated<br/>Score attached to message
%% title: Authorization flow (< 2 seconds end-to-end) · steps 4–7 sequenceDiagram autonumber participant POS as POS Terminal participant AP as Acquirer participant R as Network (Router) participant IG as Issuer Gateway participant CB as Issuer Core Banking R->>IG: authorization request IG->>CB: authorization request Note over IG,CB: Step 4 · Issuer Authorization (401-1400ms)<br/>Credit limit checked · Fraud rules evaluated<br/>Authorize/Decline decision CB-->>IG: Authorize/Decline IG-->>R: response (Issuer → Network) R-->>AP: response (Network → Acquirer) AP-->>POS: response (Acquirer → POS) Note over POS,CB: Step 5 · Response Return (1400-1800ms)<br/>Auth code generated · Response transmitted back · Receipt printed AP-)R: Step 6 · Clearing (Later, batch) R-)IG: clearing Note over AP,IG: Final transaction details · Interchange calculated · Disputes window opens Note over AP,CB: Step 7 · Settlement (T+1 to T+2)<br/>Network calculates net positions · Banks transfer funds · Merchant credited
High-level design
Q1/3 A newly onboarded bank's cards fail at the Router/Switch before ever reaching an issuer. What is the most likely missing piece?
Answer & explanation
Answer: B
The router picks the issuing bank from the card's BIN (its first 6-8 digits). Without a BIN entry there's nowhere to send the request. Interchange and settlement only matter later, during clearing.
Q2/3 The issuer is unreachable and a cardholder is at the checkout. What does stand-in processing do?
Answer & explanation
Answer: D
Stand-in makes the decision on the issuer's behalf, but only within pre-agreed rules and limits, and it tracks card velocity while doing so. Each decision is queued so the issuer can reconcile later. Blanket approve-all or decline-all would ignore the limits the issuer set.
Q3/3 A purchase was approved at the POS a few seconds ago. Where is the money right now?
Answer & explanation
Answer: A
Authorization only approves the purchase. Clearing later finalizes the details and interchange, and at settlement the network nets the positions so the banks can transfer funds. The network routes messages and never holds the cardholder's money.
Phase 4: Deep Dives (20 minutes)#
"Great high-level design. Let's dive deeper into a few areas. Tell me more about how you'd handle the real-time authorization at 65,000 TPS."
Deep Dive 1: Real-Time Authorization at Scale (Week 1-2 Concepts)#
"This is the heart of the system. Let me explain how we achieve sub-200ms latency at 65,000 TPS."
The Problem#
Without proper optimization
- Network RTT to bank: 50-100ms
- Message parsing: 10-20ms
- BIN lookup (if naive): 5-10ms
- Fraud scoring: 50-100ms (if not optimized)
- Database writes: 20-50ms
- Total: 135-280ms just in network
But we need
- End-to-end including acquirer + issuer: < 2 seconds
- Network portion only: < 200ms
- Fraud scoring: < 1ms
- Zero message loss
The Solution: Ultra-Low Latency Architecture#
%% title: Low-latency authorization path
flowchart TD
NE["NETWORK EDGE<br/>Dedicated leased lines (not internet)<br/>MPLS VPN with predictable latency<br/>Multiple redundant paths per connection"]:::edge
subgraph MP["Message processing"]
FP["FPGA/ASIC Parser"]:::hot
MO["Memory-only Processing"]:::core
BIN[("Pre-computed BIN Tables")]:::cache
FP ==> MO ==> BIN
end
AP2["OVERLAPPED PERSISTENCE<br/>WAL append starts on arrival, runs in parallel<br/>Response waits for it (group commit · Week 1: Write-ahead logs)<br/>Async replication to standby data centers"]:::store
NE ==> FP
MP -.-> AP2Message processing:
- Zero-copy message handling
- BIN lookup in < 1μs (in-memory hash)
- WAL write overlapped with processing (group commit), never serialised in front of it
Implementation#
# Real-Time Authorization Service
# Applies: Week 1 (Partitioning), Week 2 (Timeouts, Idempotency)
from dataclasses import dataclass
from typing import Optional
import asyncio
import time
from enum import Enum
class AuthDecision(Enum):
APPROVED = "00"
DECLINED_INSUFFICIENT_FUNDS = "51"
DECLINED_EXPIRED_CARD = "54"
DECLINED_FRAUD = "59"
SYSTEM_ERROR = "96"
ISSUER_UNAVAILABLE = "91"
@dataclass
class AuthorizationRequest:
"""ISO 8583-based authorization request."""
message_type: str # "0100" for auth request
pan: str # Primary Account Number (card number)
amount: int # In smallest currency unit (cents)
currency_code: str # "840" for USD
merchant_id: str
terminal_id: str
mcc: str # Merchant Category Code
transaction_id: str # Unique ID for idempotency
timestamp: float
@dataclass
class AuthorizationResponse:
"""Authorization response with decision."""
transaction_id: str
decision: AuthDecision
auth_code: Optional[str] = None
risk_score: Optional[int] = None
processing_time_ms: float = 0
class BINLookupService:
"""
Ultra-fast BIN lookup using in-memory hash table.
BIN (Bank Identification Number) is the first 6-8 digits of card.
Used to route to correct issuing bank.
Applies: Week 1, Day 1 - Hash partitioning for O(1) lookup
"""
def __init__(self):
# Pre-loaded hash map of BIN -> Issuer routing info
# In production: the full BIN-range table, loaded at startup
self._bin_table: dict[str, IssuerRoute] = {}
self._load_bin_table()
def _load_bin_table(self):
"""Load BIN table into memory at startup."""
# Example: Load from database into memory
# Real system has a very large number of entries
pass
def lookup(self, pan: str) -> Optional['IssuerRoute']:
"""
O(1) lookup of issuer routing information.
Takes < 1 microsecond with in-memory hash.
"""
# Try 8-digit BIN first, then 6-digit
bin_8 = pan[:8]
bin_6 = pan[:6]
return self._bin_table.get(bin_8) or self._bin_table.get(bin_6)
@dataclass
class IssuerRoute:
"""Routing information for an issuer."""
issuer_id: str
primary_endpoint: str
backup_endpoint: str
timeout_ms: int
supports_standin: bool
class AuthorizationService:
"""
Core authorization service achieving < 200ms latency.
Key optimizations:
1. WAL write overlapped with processing (not serialised)
2. In-memory BIN lookup
3. Replication/analytics persistence after response
4. Pre-established connections to issuers
Applies:
- Week 1, Day 1: Partitioning (BIN-based routing)
- Week 2, Day 1: Timeout management
- Week 2, Day 2: Idempotency keys
"""
def __init__(
self,
bin_service: BINLookupService,
fraud_service: 'FraudDetectionService',
issuer_gateway: 'IssuerGateway',
standin_service: 'StandInService',
wal: 'WriteAheadLog'
):
self.bin_service = bin_service
self.fraud_service = fraud_service
self.issuer_gateway = issuer_gateway
self.standin_service = standin_service
self.wal = wal
# Idempotency cache (Week 2, Day 2)
self._idempotency_cache: dict[str, AuthorizationResponse] = {}
async def authorize(
self,
request: AuthorizationRequest
) -> AuthorizationResponse:
"""
Process authorization request with strict latency SLA.
Target: < 200ms for network processing portion.
"""
start_time = time.monotonic()
# Step 1: Check idempotency (< 0.1ms)
# Prevents double-charging on retries
cached = self._idempotency_cache.get(request.transaction_id)
if cached:
return cached
# Step 2: Start the WAL write now so it overlaps BIN lookup,
# fraud scoring and the issuer call; it is awaited before responding
wal_future = asyncio.create_task(
self.wal.append(request)
)
# Step 3: BIN lookup (< 0.001ms)
route = self.bin_service.lookup(request.pan)
if not route:
return self._error_response(
request, AuthDecision.SYSTEM_ERROR
)
# Step 4: Fraud scoring (< 1ms)
# This happens in parallel with nothing else
risk_score = await self.fraud_service.score(request)
# Step 5: Route to issuer with timeout (variable, ~100-1000ms)
try:
response = await asyncio.wait_for(
self.issuer_gateway.authorize(request, route, risk_score),
timeout=route.timeout_ms / 1000.0
)
except asyncio.TimeoutError:
# Issuer timeout - use stand-in processing
if route.supports_standin:
response = await self.standin_service.authorize(
request, risk_score
)
else:
response = self._error_response(
request, AuthDecision.ISSUER_UNAVAILABLE
)
# Step 6: Calculate processing time
processing_time = (time.monotonic() - start_time) * 1000
response.processing_time_ms = processing_time
# Step 7: Cache for idempotency (TTL: 24 hours)
self._idempotency_cache[request.transaction_id] = response
# Step 8: Ensure WAL write completed
await wal_future
return response
def _error_response(
self,
request: AuthorizationRequest,
decision: AuthDecision
) -> AuthorizationResponse:
return AuthorizationResponse(
transaction_id=request.transaction_id,
decision=decision
)
class WriteAheadLog:
"""
Write-ahead log for transaction durability.
Applies: Week 1 - WAL for durability before processing.
Key insight: the WAL write starts as soon as the request
arrives and overlaps the rest of the processing, but the
response is sent only after it completes, so a crash can
never lose an authorization we have already answered.
"""
async def append(self, request: AuthorizationRequest) -> None:
"""
Append request to WAL.
In production: Write to local SSD with group commit
for batching multiple transactions per fsync.
"""
# Serialize and write to durable storage
pass
# =============================================================================
# Network Layer: ISO 8583 Message Handling
# =============================================================================
class ISO8583Parser:
"""
High-performance ISO 8583 message parser.
In production, this might be implemented in:
- C++ for CPU optimization
- FPGA for hardware acceleration
- Specialized network appliances
Key fields in ISO 8583:
- Field 2: Primary Account Number (PAN)
- Field 3: Processing Code
- Field 4: Transaction Amount
- Field 11: System Trace Audit Number
- Field 37: Retrieval Reference Number
- Field 39: Response Code
"""
@staticmethod
def parse(raw_message: bytes) -> AuthorizationRequest:
"""
Parse ISO 8583 message to internal format.
Real implementation handles:
- Multiple MTI versions (1987, 1993, 2003)
- Network-specific variations
- BCD vs ASCII encoding
- Variable-length fields
"""
# Parse MTI (Message Type Indicator)
# Parse bitmap to know which fields present
# Parse each field according to spec
pass
@staticmethod
def serialize(response: AuthorizationResponse) -> bytes:
"""Serialize response to ISO 8583 format."""
passEdge Cases#
"What happens if the issuer is slow or unreachable?"
The issuer isn't answering
The shopper is standing at the till and the issuing bank hasn't replied. Do you approve, decline or keep waiting? What would you need in order to decide safely on the issuer's behalf?
One strong answer
- Wait, but only within a budget: tiered timeouts keep the whole attempt to about a second, leaving time for the acquirer and the terminal.
- Then stand in: decide using rules the issuer agreed in advance, such as a single-transaction limit, a daily velocity limit, a risk-score cutoff and the lost-or-stolen card list.
- Have the data ready: per-card velocity counters the network maintains itself, so stand-in needs nothing from the issuer.
- Reconcile afterwards: queue every stand-in decision for the issuer, which accepts liability for approvals made within its rules.
The shopper gets an answer, the issuer's risk stays bounded, and nothing is lost.
What is published: Visa's FY2025 Form 10-K describes stand-in processing that authorizes transactions on behalf of issuers during outages (its AI version is called Smarter Stand-In Processing). The tiers, limits and thresholds below are a likely design, not Visa's published parameters.
"We implement stand-in processing with tiered timeouts..."
Scenario: Issuer taking too long or unreachable
Timeout Strategy (Week 2, Day 1):
%% title: Issuer timeout handling — tiered timeouts flowchart TD T1["Timeout Tier 1: 500ms<br/>First attempt to primary endpoint"]:::core T2["Timeout Tier 2: 300ms<br/>Failover to backup endpoint"]:::service T3["Timeout Tier 3: 200ms (Stand-In)<br/>Make decision locally using issuer-provided rules"]:::queue T1 -->|"timeout"| T2 T2 -->|"timeout"| T3
Stand-in rules (issuer-provided):
- Single transaction limit: $500
- Daily velocity limit: $2,000
- Decline if risk score > 80
- Decline if card reported lost/stolen
Total budget: ~1,000ms (leaves time for acquirer/merchant)
Stand-In Processing:
- Track card velocity in Redis (global cluster)
- Apply issuer-defined rules
- Queue for later reconciliation with issuer
- Issuer accepts liability for approved stand-ins
A $100 purchase goes POS → acquirer → network, where a BIN lookup finds the issuer and the fraud engine adds a risk score in about 1 ms; the issuer approves and the auth code is back at the POS by about 1,800 ms, while money moves only at settlement (T+1 to T+2). If the issuer doesn't answer, the network tries its primary endpoint (500 ms) and backup (300 ms), then stand-in decides in 200 ms on the issuer's rules ($500 single limit, $2,000 daily, risk ≤ 80, not lost or stolen) and queues the decision for reconciliation.
Deep Dive 2: Real-Time Fraud Detection in < 1ms (Week 1-2 Concepts)#
"How do you score 500+ attributes for fraud in under a millisecond?"
Five hundred attributes in one millisecond
Scoring has to sit inline with authorization and take under a millisecond. What must already be true before the transaction arrives for that to be possible?
One strong answer
- No network calls on the hot path: the features are already in memory. Velocity counters (transactions in the last hour, amount today, unique merchants) are updated as transactions stream past, and cardholder profiles are cached next to the scorer.
- A model built for inference speed: weights loaded in memory, compiled ahead of time, run in-process on optimised CPUs or accelerators.
- A hard deadline with a fallback: if scoring overruns, the transaction proceeds on rules rather than waiting.
- Heavy work elsewhere: training, graph analysis and investigation run offline and only feed the online model and its features.
"This is where AI meets extreme performance engineering. Let me show you the architecture..."
The Solution: ML at Millisecond Scale#
%% title: Fraud detection architecture
flowchart TD
TX(["Transaction"]):::client
subgraph FE["Feature extraction (< 0.5ms)"]
direction TB
TF["Transaction Features<br/>Amount · Currency · MCC category<br/>Card present/absent<br/>Entry mode (chip/swipe)"]:::service
VF["Velocity Features<br/>Txn count last hour · last day<br/>Amount last hour<br/>Unique merchants today<br/>Geographic spread"]:::cache
BF["Behavioral Features<br/>Time since last txn<br/>Distance from last txn<br/>Deviation from pattern<br/>Device fingerprint"]:::service
RI["Risk Indicators<br/>Is high-risk MCC? · Is high-risk country?<br/>Card age<br/>Address verification result"]:::service
end
MS["MODEL SCORING (< 0.5ms)<br/>Neural Network · pre-compiled for inference<br/>Weights loaded in memory<br/>GPU/TPU acceleration or optimized CPU<br/>Batch scoring for throughput"]:::core
SC{"Score<br/>Risk score 0-99"}:::hot
TX ==> FE
FE ==> MS
MS ==> SC
SC -->|"0-30"| LOW["Low risk<br/>(auto-approve candidate)"]:::ok
SC -->|"30-70"| MED["Medium risk<br/>(standard processing)"]:::service
SC -->|"70-90"| HIGH["High risk<br/>(additional auth may be required)"]:::queue
SC -->|"90-99"| VH["Very high risk<br/>(likely fraud, decline candidate)"]:::badImplementation#
# Real-Time Fraud Detection Service
# Applies: Week 1 (Data partitioning for velocity), Week 2 (Timeouts)
import asyncio
import time
import numpy as np
from typing import Dict, List
import redis.asyncio as redis
from dataclasses import dataclass
@dataclass
class FraudFeatures:
"""500+ features extracted for fraud scoring."""
# Transaction features (from request)
amount: float
currency: str
mcc: str
entry_mode: str
card_present: bool
# Velocity features (from Redis)
txn_count_1h: int
txn_count_24h: int
amount_sum_1h: float
amount_sum_24h: float
unique_merchants_24h: int
unique_countries_24h: int
# Behavioral features (computed)
time_since_last_txn_seconds: float
distance_from_last_txn_km: float
amount_deviation_from_avg: float
# Risk indicators
is_high_risk_mcc: bool
is_high_risk_country: bool
card_age_days: int
def to_vector(self) -> np.ndarray:
"""Convert to feature vector for model input."""
# One-hot encode categorical features
# Normalize numerical features
# Return as numpy array
pass
class VelocityService:
"""
Track card-level velocity using Redis.
Applies: Week 1, Day 4 - Hot key handling
Challenge: Popular cards (corporate cards) can be hot keys.
Solution: Use Redis Cluster with card-hash-based sharding.
"""
def __init__(self, redis_cluster: redis.RedisCluster):
self.redis = redis_cluster
async def get_velocity(self, pan_hash: str) -> Dict[str, any]:
"""
Get velocity metrics for a card.
Uses Redis sorted sets for time-windowed counting.
All operations are O(log N) or better.
"""
pipe = self.redis.pipeline()
now = time.time()
# Key structure: velocity:{pan_hash}:{metric}
base_key = f"velocity:{pan_hash}"
# Count transactions in last hour
pipe.zcount(f"{base_key}:txns", now - 3600, now)
# Count transactions in last 24 hours
pipe.zcount(f"{base_key}:txns", now - 86400, now)
# Amounts in last hour (member = "ts:amount", score = ts)
pipe.zrangebyscore(
f"{base_key}:amounts",
now - 3600,
now
)
# Unique merchants in last 24 hours
pipe.zcount(f"{base_key}:merchants", now - 86400, now)
# Last transaction location
pipe.get(f"{base_key}:last_location")
results = await pipe.execute()
return {
"txn_count_1h": results[0],
"txn_count_24h": results[1],
"amount_sum_1h": sum(float(m.split(":")[1]) for m in results[2]),
"unique_merchants_24h": results[3],
"last_location": results[4]
}
async def record_transaction(
self,
pan_hash: str,
amount: float,
merchant_id: str,
location: str
) -> None:
"""
Record transaction for future velocity checks.
Uses async fire-and-forget to not block auth response.
"""
pipe = self.redis.pipeline()
now = time.time()
base_key = f"velocity:{pan_hash}"
# Add to transaction count
pipe.zadd(f"{base_key}:txns", {str(now): now})
# Add amount (scored by time so windows and cleanup work)
pipe.zadd(f"{base_key}:amounts", {f"{now}:{amount}": now})
# Add merchant
pipe.zadd(f"{base_key}:merchants", {merchant_id: now})
# Update last location
pipe.set(f"{base_key}:last_location", location)
# Expire old data (48 hour window for cleanup)
for key_suffix in ["txns", "amounts", "merchants"]:
pipe.zremrangebyscore(
f"{base_key}:{key_suffix}",
0,
now - 172800
)
await pipe.execute()
class FraudDetectionService:
"""
Real-time fraud scoring in < 1ms.
Applies:
- Week 1, Day 4: Hot key handling for velocity
- Week 2, Day 1: Strict timeout management
Key optimizations:
1. Pre-loaded model weights in memory
2. Batch inference when possible
3. Feature computation parallelized
4. Redis cluster for velocity data
"""
def __init__(
self,
velocity_service: VelocityService,
model: 'FraudModel'
):
self.velocity = velocity_service
self.model = model
# Pre-compute static risk indicators
self._high_risk_mccs = self._load_high_risk_mccs()
self._high_risk_countries = self._load_high_risk_countries()
async def score(self, request: AuthorizationRequest) -> int:
"""
Score transaction for fraud risk.
Returns: Risk score 0-99
Target latency: < 1ms
"""
# Hash PAN for privacy and consistent sharding
pan_hash = self._hash_pan(request.pan)
# Get velocity features (Redis, < 0.3ms)
velocity = await self.velocity.get_velocity(pan_hash)
# Extract all features (< 0.2ms)
features = self._extract_features(request, velocity)
# Run model inference (< 0.5ms)
score = self.model.predict(features.to_vector())
# Fire-and-forget: Record this transaction for future velocity
asyncio.create_task(
self.velocity.record_transaction(
pan_hash,
request.amount,
request.merchant_id,
self._get_location(request)
)
)
return int(score * 99)
def _extract_features(
self,
request: AuthorizationRequest,
velocity: Dict
) -> FraudFeatures:
"""Extract features from request and velocity data."""
# Compute geographic distance if we have last location
distance_km = 0.0
if velocity.get("last_location"):
distance_km = self._calculate_distance(
velocity["last_location"],
self._get_location(request)
)
return FraudFeatures(
amount=request.amount / 100.0, # Convert cents to dollars
currency=request.currency_code,
mcc=request.mcc,
entry_mode="chip", # From request
card_present=True, # From request
txn_count_1h=velocity.get("txn_count_1h", 0),
txn_count_24h=velocity.get("txn_count_24h", 0),
amount_sum_1h=velocity.get("amount_sum_1h", 0.0),
amount_sum_24h=velocity.get("amount_sum_24h", 0.0),
unique_merchants_24h=velocity.get("unique_merchants_24h", 0),
unique_countries_24h=1, # Computed from history
time_since_last_txn_seconds=0.0, # Computed
distance_from_last_txn_km=distance_km,
amount_deviation_from_avg=0.0, # Computed
is_high_risk_mcc=request.mcc in self._high_risk_mccs,
is_high_risk_country=False, # From merchant location
card_age_days=365 # From card data
)
def _hash_pan(self, pan: str) -> str:
"""Hash PAN for privacy-preserving velocity lookup."""
import hashlib
return hashlib.sha256(pan.encode()).hexdigest()[:16]
def _load_high_risk_mccs(self) -> set:
"""Load high-risk merchant category codes."""
return {
"5912", # Drug stores
"5944", # Jewelry stores
"5999", # Misc retail
"7995", # Gambling
}
def _load_high_risk_countries(self) -> set:
"""Load high-risk countries."""
return set() # Configured per issuer
class FraudModel:
"""
Pre-trained neural network for fraud scoring.
In production:
- Trained on billions of transactions
- Updated weekly with new fraud patterns
- A/B tested before deployment
- Multiple model versions for different card types
"""
def __init__(self, model_path: str):
# Load pre-trained model weights
# Could be TensorFlow, PyTorch, or ONNX
self.weights = self._load_weights(model_path)
def predict(self, features: np.ndarray) -> float:
"""
Run inference on feature vector.
Returns: Probability of fraud (0.0 to 1.0)
In production, this might use:
- TensorRT for GPU acceleration
- ONNX Runtime for CPU optimization
- Custom inference engine
"""
# Simple neural network forward pass
# Real implementation is much more sophisticated
return 0.1 # Placeholder
def _load_weights(self, path: str):
"""Load model weights from file."""
passAuthorization and fraud scoring
Q1/3 An acquirer times out and resends an authorization with the same
transaction_id, but the original was already approved. What does the service do?Answer & explanation
Answer: D
The first step of
authorizeis the idempotency check. A repeattransaction_idgets the stored response back, so the customer isn't charged twice. Resending to the issuer is exactly the double-charge risk the cache exists to prevent.Q2/3 The issuer's primary endpoint doesn't respond within its 500ms tier. What happens next?
Answer & explanation
Answer: B
The tiers run primary (500ms), then backup (300ms), then stand-in (200ms), all inside a budget of about 1,000ms. Retrying the slow primary with backoff would burn the time the merchant and acquirer still need.
Q3/3 Fraud scoring must use velocity features like "transactions in the last hour" and still finish in under 1ms. Which design makes that possible?
Answer & explanation
Answer: C
Sorted sets keyed by hashed PAN give windowed counts with
ZCOUNT, and a single pipeline fetches all the metrics at once. A database scan per auth would be far too slow. Nightly batches would miss the burst that card testing produces.
Deep Dive 3: Global Data Center Architecture (Week 1-2 Concepts)#
"How do you achieve 99.9999% uptime across global data centers?"
"This requires a sophisticated multi-data-center architecture with synchronous and asynchronous replication..."
The Architecture#
%% title: Global data center topology
flowchart TD
LON@{ icon: "sd:servers", label: "LONDON DC<br/>(Active)" }
ASH@{ icon: "sd:servers", label: "ASHBURN DC<br/>(Primary US)" }
DEN@{ icon: "sd:servers", label: "DENVER DC<br/>(Backup US)" }
SIN@{ icon: "sd:servers", label: "SINGAPORE DC<br/>(APAC Primary)" }
LON --> ASH & DEN & SIN
ASH <--> DEN
DEN <--> SIN
MPLS@{ icon: "sd:network", label: "MPLS VPN Network<br/>(Dedicated circuits, not public internet)" }
ASH --> MPLS
SIN --> MPLS
MPLS --> ACQB@{ icon: "sd:bank", label: "Acquirer Banks<br/>(Connections)" }
MPLS --> ISSB@{ icon: "sd:bank", label: "Issuer Banks<br/>(Connections)" }
class LON,ASH,SIN core
class DEN service
class MPLS edge
class ACQB,ISSB externalREPLICATION STRATEGY:
- Auth state: Synchronous within region, async across regions
- Transaction log: Multi-master with conflict resolution
- BIN tables: Read replicas everywhere, writes to primary
- Velocity data: Redis Cluster spanning regions
Redundancy Design#
# Multi-Data-Center Routing and Failover
# Applies: Week 1, Day 2 - Replication Trade-offs
from dataclasses import dataclass
from typing import List, Optional
from enum import Enum
import asyncio
class DataCenterStatus(Enum):
HEALTHY = "healthy"
DEGRADED = "degraded"
OFFLINE = "offline"
@dataclass
class DataCenter:
"""Data center configuration and health."""
id: str
region: str
primary_for_regions: List[str]
status: DataCenterStatus
latency_ms: float
capacity_pct: float
class GlobalRouter:
"""
Route requests to optimal data center.
Applies: Week 1, Day 2 - Replication and failover
Routing priorities:
1. Geographic proximity (latency)
2. Data center health
3. Load balancing
4. Regulatory requirements (data residency)
"""
def __init__(self):
self.data_centers: Dict[str, DataCenter] = {}
self._health_check_interval = 1.0 # seconds
def select_data_center(
self,
source_region: str,
transaction_type: str
) -> DataCenter:
"""
Select optimal data center for processing.
Returns primary DC for region if healthy,
otherwise fails over to backup.
"""
# Get primary DC for this region
primary = self._get_primary_for_region(source_region)
if primary and primary.status == DataCenterStatus.HEALTHY:
return primary
# Primary unhealthy - find backup
backup = self._get_backup_for_region(source_region)
if backup and backup.status == DataCenterStatus.HEALTHY:
return backup
# All regional DCs down - use global fallback
return self._get_any_healthy_dc()
def _get_primary_for_region(self, region: str) -> Optional[DataCenter]:
"""Get primary DC for a region."""
for dc in self.data_centers.values():
if region in dc.primary_for_regions:
return dc
return None
class TransactionReplicator:
"""
Replicate transactions across data centers.
Applies: Week 1, Day 2 - Sync vs Async replication
Strategy:
- Authorization state: Sync within region (strong consistency)
- Transaction log: Async across regions (eventual consistency)
- WAL replication ensures no data loss on DC failure
"""
def __init__(
self,
local_dc: str,
peer_dcs: List[str]
):
self.local_dc = local_dc
self.peer_dcs = peer_dcs
async def replicate_auth(
self,
transaction: AuthorizationRequest,
response: AuthorizationResponse
) -> None:
"""
Replicate authorization to peer DCs.
Sync replication to regional peer (for hot standby).
Async replication to other regions (for DR).
"""
# Sync replicate to regional peer
regional_peer = self._get_regional_peer()
if regional_peer:
await self._sync_replicate(regional_peer, transaction, response)
# Async replicate to other regions
for dc in self.peer_dcs:
if dc != regional_peer:
asyncio.create_task(
self._async_replicate(dc, transaction, response)
)
async def _sync_replicate(
self,
dc: str,
transaction: AuthorizationRequest,
response: AuthorizationResponse
) -> None:
"""
Synchronous replication - wait for acknowledgment.
Used for regional failover capability.
Timeout: 50ms (if peer is slow, continue anyway)
"""
try:
await asyncio.wait_for(
self._send_to_dc(dc, transaction, response),
timeout=0.05
)
except asyncio.TimeoutError:
# Log but don't fail the auth
# Regional peer will catch up from WAL
pass
async def _async_replicate(
self,
dc: str,
transaction: AuthorizationRequest,
response: AuthorizationResponse
) -> None:
"""
Asynchronous replication - fire and forget.
Used for cross-region disaster recovery.
Will be caught up from WAL if this fails.
"""
try:
await self._send_to_dc(dc, transaction, response)
except Exception as e:
# Log error - cross-region replication will recover
pass
class DataCenterFailover:
"""
Handle data center failover scenarios.
Applies: Week 2, Day 3 - Circuit breakers
Failover scenarios:
1. Network partition between DCs
2. Complete DC outage
3. Degraded performance (slow responses)
"""
def __init__(self):
self._dc_health: Dict[str, CircuitBreaker] = {}
async def check_dc_health(self, dc_id: str) -> DataCenterStatus:
"""
Check health of a data center.
Uses circuit breaker pattern to avoid
cascading failures.
"""
breaker = self._dc_health.get(dc_id)
if breaker and breaker.is_open:
return DataCenterStatus.OFFLINE
try:
# Send health check
latency = await self._ping_dc(dc_id)
if latency > 100: # ms
return DataCenterStatus.DEGRADED
return DataCenterStatus.HEALTHY
except Exception:
# Record failure
if breaker:
breaker.record_failure()
return DataCenterStatus.OFFLINEPhysical Resilience#
Data center physical design
Power
- Multiple independent utility feeds
- On-site diesel generators (28 MW of backup power)
- ~100,000 gallons of fuel for about 5 days of autonomy
- Uninterruptible power supply (UPS) with battery backup
- Fully redundant power systems
Cooling
- ~1.5 million gallon (5.6 million litre) water storage tank
- 5,750 tons of cooling capacity, with backup cooling
Physical security
- 18-inch reinforced concrete walls
- Designed for 170 mph winds
- Earthquake resistant (reported as magnitude 7.0)
- Hydraulic bollards (stop 50 mph vehicles)
- Multi-layer biometric access
- Only 75 Visa employees worldwide cleared to enter the data halls
Network
- Multiple fiber routes from different carriers
- Private network (not public internet)
- 10+ million route miles of private telecom network (Visa, 2017)
- Redundant connections to client banks
Deep Dive 4: Clearing and Settlement (Week 3 Concepts)#
"Walk me through how money actually moves after authorization."
"Clearing and settlement is where the financial reality catches up with the real-time authorization. Let me explain the batch processing system..."
Clearing and Settlement Flow#
- T+0 (Authorization Day) · 12:00 PM — Customer swipes card at merchant · Authorization approved (real-time) · Money NOT moved yet
- T+0 (End of Day) · 11:59 PM — Merchant batches all day's transactions · Sends clearing file to acquirer · Acquirer sends to network
- T+1 (Clearing Day) · 2:00 AM — Network processes clearing files · Matches clearing to authorizations · Calculates interchange fees · Nets positions across all banks
- T+1 (Clearing Day) · 6:00 AM — Network sends settlement files to banks · Each bank knows net debit or credit
- T+1/T+2 (Settlement) · 9:00 AM — Banks with net debit wire funds to network · Network distributes to banks with net credit · Usually via Fedwire or SWIFT
- Result — Merchant's bank account credited · Customer's statement shows charge · Interchange fee collected
Fee Calculation#
Transaction: $100 purchase at restaurant
Customer pays: $100.00
Breakdown
- Merchant receives: $97.50
- Acquirer keeps: $0.30 (processing fee)
- Network keeps: $0.20 (scheme fee)
- Issuer receives: $2.00 (interchange fee)
Interchange varies by
- Card type (credit vs debit, premium vs standard)
- Merchant category (restaurant, grocery, gas station)
- Transaction type (card present vs card not present)
- Risk level (chip vs swipe vs online)
Typical total merchant cost (US credit cards): 1.5% - 3.5% of transaction; regulated US debit interchange is far lower
Implementation#
# Clearing and Settlement Service
# Applies: Week 3 (Messaging, Batch Processing)
from dataclasses import dataclass
from typing import List, Dict
from decimal import Decimal
from datetime import datetime, date
import asyncio
@dataclass
class ClearingRecord:
"""Clearing record submitted by acquirer."""
transaction_id: str
authorization_code: str
pan_hash: str
amount: Decimal
currency: str
merchant_id: str
mcc: str
acquirer_bin: str
issuer_bin: str
transaction_date: date
clearing_date: date
@dataclass
class InterchangeFee:
"""Calculated interchange fee."""
transaction_id: str
rate: Decimal # Percentage
fixed_fee: Decimal # Fixed amount
total_fee: Decimal
fee_category: str
@dataclass
class SettlementPosition:
"""Net settlement position for a bank."""
bank_id: str
net_amount: Decimal # Positive = receives, Negative = pays
transaction_count: int
interchange_received: Decimal
interchange_paid: Decimal
class ClearingService:
"""
Process clearing files and prepare settlement.
Applies: Week 3, Day 1 - Batch vs Stream processing
This is primarily batch processing:
- Runs daily after clearing cutoff
- Processes millions of records
- Must be idempotent (can re-run safely)
"""
def __init__(
self,
interchange_calculator: 'InterchangeCalculator',
settlement_service: 'SettlementService'
):
self.interchange = interchange_calculator
self.settlement = settlement_service
async def process_clearing_batch(
self,
clearing_records: List[ClearingRecord]
) -> List[SettlementPosition]:
"""
Process a batch of clearing records.
Steps:
1. Validate each record against authorization
2. Calculate interchange fees
3. Net positions across all banks
4. Generate settlement file
"""
# Group by acquirer and issuer
by_acquirer: Dict[str, List[ClearingRecord]] = {}
by_issuer: Dict[str, List[ClearingRecord]] = {}
for record in clearing_records:
# Validate against authorization
if not await self._validate_against_auth(record):
continue
# Calculate interchange
fee = self.interchange.calculate(record)
# Add to acquirer's batch
if record.acquirer_bin not in by_acquirer:
by_acquirer[record.acquirer_bin] = []
by_acquirer[record.acquirer_bin].append(record)
# Add to issuer's batch
if record.issuer_bin not in by_issuer:
by_issuer[record.issuer_bin] = []
by_issuer[record.issuer_bin].append(record)
# Calculate net positions
positions = self._calculate_net_positions(
by_acquirer, by_issuer
)
# Send to settlement
await self.settlement.initiate_settlement(positions)
return positions
async def _validate_against_auth(
self,
record: ClearingRecord
) -> bool:
"""
Validate clearing record matches authorization.
Checks:
- Authorization exists
- Amounts match (within tolerance)
- Not already cleared
- Within clearing window
"""
# Look up original authorization
# Compare amounts (allow small differences for tips)
# Ensure not duplicate
return True
def _calculate_net_positions(
self,
by_acquirer: Dict[str, List[ClearingRecord]],
by_issuer: Dict[str, List[ClearingRecord]]
) -> List[SettlementPosition]:
"""
Calculate net settlement position for each bank.
Issuers: pay the transaction amounts minus interchange
(they bill their cardholders for the full amount)
Acquirers: receive the amounts minus interchange, then pay
their merchants (minus the acquirer's own fees)
Net it all together so each bank has single debit/credit.
"""
positions: Dict[str, SettlementPosition] = {}
# Process acquirer side (they receive amount minus interchange)
for acquirer_bin, records in by_acquirer.items():
total_amount = sum(r.amount for r in records)
total_interchange = sum(
self.interchange.calculate(r).total_fee
for r in records
)
if acquirer_bin not in positions:
positions[acquirer_bin] = SettlementPosition(
bank_id=acquirer_bin,
net_amount=Decimal(0),
transaction_count=0,
interchange_received=Decimal(0),
interchange_paid=Decimal(0)
)
positions[acquirer_bin].net_amount += (
total_amount - total_interchange
)
positions[acquirer_bin].interchange_paid += total_interchange
positions[acquirer_bin].transaction_count += len(records)
# Process issuer side (they pay the transaction amounts)
for issuer_bin, records in by_issuer.items():
total_amount = sum(r.amount for r in records)
total_interchange = sum(
self.interchange.calculate(r).total_fee
for r in records
)
if issuer_bin not in positions:
positions[issuer_bin] = SettlementPosition(
bank_id=issuer_bin,
net_amount=Decimal(0),
transaction_count=0,
interchange_received=Decimal(0),
interchange_paid=Decimal(0)
)
# Issuer pays: amount minus the interchange it keeps
positions[issuer_bin].net_amount -= (
total_amount - total_interchange
)
positions[issuer_bin].interchange_received += total_interchange
return list(positions.values())
class InterchangeCalculator:
"""
Calculate interchange fees based on transaction characteristics.
Interchange varies by:
- Card type (credit, debit, premium, corporate)
- Merchant category code
- Transaction type (card present, CNP, recurring)
- Geographic factors
"""
def __init__(self):
# Load interchange rate tables
# Visa and Mastercard publish these twice yearly
self._rate_tables = self._load_rate_tables()
def calculate(self, record: ClearingRecord) -> InterchangeFee:
"""Calculate interchange fee for a transaction."""
# Look up rate based on characteristics
rate_info = self._lookup_rate(
card_type=self._get_card_type(record.pan_hash),
mcc=record.mcc,
card_present=True, # Derived from clearing data
transaction_type="purchase"
)
# Calculate fee
percentage_fee = record.amount * rate_info["rate"]
fixed_fee = Decimal(rate_info["fixed"])
total = percentage_fee + fixed_fee
return InterchangeFee(
transaction_id=record.transaction_id,
rate=rate_info["rate"],
fixed_fee=fixed_fee,
total_fee=total,
fee_category=rate_info["category"]
)
def _lookup_rate(
self,
card_type: str,
mcc: str,
card_present: bool,
transaction_type: str
) -> Dict:
"""Look up interchange rate from tables."""
# Complex logic based on Visa/Mastercard published rates
# Example rates:
return {
"rate": Decimal("0.0185"), # 1.85%
"fixed": Decimal("0.10"), # 10 cents
"category": "standard_credit_purchase"
}
def _load_rate_tables(self) -> Dict:
"""Load interchange rate tables."""
# Published by Visa and Mastercard
return {}
class SettlementService:
"""
Execute settlement between banks.
Settlement happens via:
- Fedwire (US domestic)
- SWIFT (International)
- Central bank systems
"""
async def initiate_settlement(
self,
positions: List[SettlementPosition]
) -> None:
"""
Initiate settlement transfers.
Process:
1. Send debit instructions to banks that owe
2. Wait for funds to arrive in clearing account
3. Send credit instructions to banks that receive
"""
# Banks that owe (negative position)
debits = [p for p in positions if p.net_amount < 0]
# Banks that receive (positive position)
credits = [p for p in positions if p.net_amount > 0]
# Request debits first
for position in debits:
await self._request_debit(
position.bank_id,
abs(position.net_amount)
)
# Wait for funds to arrive (usually within hour)
await self._wait_for_funds()
# Send credits
for position in credits:
await self._send_credit(
position.bank_id,
position.net_amount
)
async def _request_debit(
self,
bank_id: str,
amount: Decimal
) -> None:
"""Request debit from bank via Fedwire."""
# Send Fedwire 1031 drawdown request
pass
async def _send_credit(
self,
bank_id: str,
amount: Decimal
) -> None:
"""Send credit to bank via Fedwire."""
# Send Fedwire funds transfer
passData centers and clearing
Q1/3 Why replicate authorization state synchronously only to the regional peer and asynchronously to other regions?
Answer & explanation
Answer: B
A sync regional peer gives a hot standby at low latency. Waiting for every distant region on each auth would add long round trips to the critical path. Async replication with WAL catch-up still protects against losing a whole region.
Q2/3 The regional peer is slow and the 50ms synchronous replication call times out. What happens to the authorization?
Answer & explanation
Answer: D
The sync step logs the timeout and doesn't fail the auth, because a slow replica shouldn't turn into a failed checkout. The WAL is what lets the peer recover the missed records afterwards.
Q3/3 Clearing nets every bank's activity into a single debit or credit instead of moving money per transaction. What's the main benefit?
Answer & explanation
Answer: C
With netting, each bank ends up with one net position, so banks with a net debit pay in once and banks with a net credit get paid once. Matching against authorizations and calculating interchange still happen before the positions are netted.
Phase 5: Scaling and Edge Cases (5 minutes)#
"How would this system scale to 10x the current load?"
Scaling Strategy#
"The system is designed for horizontal scaling at multiple layers..."
Scaling strategy
| Component | Current | 10X scale |
|---|---|---|
| Message Gateways | 100 nodes (Stateless, add more) | 1,000 nodes (Same architecture) |
| Router/Switch | 50 nodes (BIN tables fit in RAM) | 500 nodes (Partition by BIN range) |
| Fraud Detection | 200 nodes (Model inference) | 2,000 nodes (More GPU nodes) |
| Redis Cluster | 50 shards (Velocity data) | 500 shards (Re-shard by PAN hash) |
| Data Centers | 4 active | 7+ active (Add APAC, LATAM capacity) |
| Issuer Connections | 15,000 banks | Same (banks add capacity) |
Edge Cases#
"What are some edge cases we should handle?"
Edge Case 1: Duplicate Authorization Requests#
Problem
- Customer could be double-charged
Solution (Week 2, Day 2 - Idempotency)
- Every auth request has unique
transaction_id - Cache auth responses for 24 hours
- Return cached response on duplicate request
- Idempotency key =
acquirer_id+transaction_id(a repeat message carries the original's STAN and transmission time unchanged; never use the retry's own timestamp)
Edge Case 2: Authorization/Clearing Mismatch#
Examples
- Restaurant: Auth $50, Clearing $60 (tip added)
- Gas station: Auth $100 (hold), Clearing $42 (actual pump)
- Hotel: Auth $500, Clearing $650 (incidentals)
Solution
- Allow clearing within tolerance of auth
- Partial clearing allowed
- Over-tolerance triggers issuer notification
- Some MCCs have special rules (gas: a fixed pre-authorization hold, then clear the actual pump amount)
Edge Case 3: Issuer Timeout During High Volume#
Problem
- Issuer latency increases from 200ms to 5s
- Customers abandon purchases
- Merchant loses sales
Solution (Stand-In Processing)
- Detect issuer degradation (latency > threshold)
- Switch to stand-in mode for that issuer
- Apply issuer-defined rules:
- Single txn limit: $500
- Velocity limit: 10 txn/hour
- Decline if card flagged
- Queue for later reconciliation
- Issuer accepts liability for stand-in approvals
Failure Scenarios#
| Failure | Detection | Impact | Recovery |
|---|---|---|---|
| Data center outage | Health checks, latency | Route to backup DC | Automatic failover < 30s |
| Redis cluster failure | Connection errors | Velocity checks fail | Fall back to conservative limits |
| Fraud model timeout | Latency > 1ms | Transaction delays | Bypass fraud (use stand-in rules) |
| Issuer unreachable | Timeouts | Can't authorize | Stand-in processing |
| Network partition | Split-brain detection | Inconsistent state | Prefer availability, reconcile later |
Scaling and edge cases
Q1/3 You need 10x capacity. Which component scales just by adding nodes, with no repartitioning?
Answer & explanation
Answer: A
The gateways hold no state, so you just add more of them. Redis has to be re-sharded by PAN hash, and at 10x the router partitions by BIN range. Issuer capacity is on the banks.
Q2/3 A gas station authorizes $100 but the clearing record arrives for $42. How should clearing handle it?
Answer & explanation
Answer: C
The auth was only a hold. Partial clearing within tolerance is expected for gas, tips and hotel incidentals, and only clearing over tolerance triggers an issuer notification. Treating the difference as a mismatch would flag ordinary purchases.
Q3/3 The Redis velocity cluster fails. What is the recommended behavior for authorizations?
Answer & explanation
Answer: B
Losing velocity features weakens fraud checks but shouldn't stop commerce, so the system degrades to conservative limits. Declining everything turns a cache outage into a full network outage, and approving everything opens the door to fraud.
Phase 6: Monitoring and Operations#
"How would you monitor this system in production?"
Key Metrics#
Authorization health
| Metric | Value | Status |
|---|---|---|
| TPS current | 45,231 /sec | 70% |
| Auth latency p99 | 127ms | OK |
| Approval rate | 96.2% | OK |
| Stand-in rate | 0.3% | OK |
Fraud detection
| Metric | Value | Status |
|---|---|---|
| Scoring latency p99 | 0.8ms | OK |
| High-risk flagged | 0.5% | OK |
| False positive rate | 0.08% | OK |
Data center health
| Data center | Status | CPU | Mem |
|---|---|---|---|
| Ashburn | Healthy | 45% | 62% |
| Denver | Healthy | 38% | 58% |
| London | Healthy | 52% | 65% |
| Singapore | Healthy | 41% | 55% |
Issuer connectivity
- Connected issuers: 14,995 / 15,000 (includes the degraded ones)
- Degraded issuers: 23
- Offline issuers: 5
Clearing & settlement
- Pending clearing: 2.3M records
- Settlement status: T+1 complete
- Unmatched auths: 0.02%
Alerting Strategy#
CRITICAL (PagerDuty, immediate)
- Authorization latency p99 > 500ms
- Approval rate drop > 5% in 5 minutes
- Data center offline
- Stand-in rate > 5%
- Security breach detected
WARNING (Slack, 15 min)
- Authorization latency p99 > 200ms
- Single issuer degraded > 5 minutes
- Fraud score latency > 2ms
- Clearing match rate < 99%
INFO (Dashboard only)
- TPS fluctuations
- Routine issuer timeouts
- Scheduled maintenance
Runbook: High Authorization Latency#
Symptoms
- p99 latency > 200ms
- Customer complaints about slow checkout
- Acquirer timeout rates increasing
Diagnosis
- Check issuer latency breakdown:
$ auth-latency-breakdown --last 5m - Identify slow issuers:
$ issuer-latency-report --threshold 500ms - Check fraud scoring latency:
$ fraud-latency-percentiles --last 5m - Check DC health:
$ dc-health-status --all
Resolution
If single issuer slow:
- Enable stand-in for that issuer
- Alert issuer operations team
- Monitor stand-in approval quality
If fraud scoring slow:
- Check model serving cluster health
- Scale up GPU nodes if needed
- Consider bypassing fraud for low-risk
If DC overloaded:
- Shift traffic to backup DC
- Scale up compute in affected DC
- Investigate traffic spike cause
Escalation
- Network Operations Center (NOC)
- Risk Operations Center (ROC) if fraud-related
- Issuer relations if bank-specific
Running the network
Q1/3 The stand-in rate climbs from 0.3% to 6% in a few minutes. What does that most likely mean?
Answer & explanation
Answer: B
Stand-in only happens when issuers are slow or unreachable. A jump past the 5% critical threshold says that a large share of decisions are being made without the issuer, which is why it pages immediately.
Q2/3 p99 authorization latency is above 200 ms, and the breakdown shows a single issuer answering in 2 seconds. Following the runbook, what comes first?
Answer & explanation
Answer: C
One slow issuer shouldn't hold up its cardholders at every till. Stand-in answers on its behalf within its rules, while the bank fixes its own systems. Moving data centres wouldn't speed up the issuer.
Q3/3 Why page on "approval rate drops more than 5% in 5 minutes" even when latency looks healthy?
Answer & explanation
Answer: B
A bad rule, a faulty model push or an issuer problem can decline genuine payments quickly, so latency stays green while checkouts fail. Watch outcomes as well as speed.
Final Challenge: The Card-Testing Bot#
The interviewer pulls up one more slide. "This one happens every week somewhere."
"A botnet starts sending tiny $1 card-not-present authorizations to thousands of online merchants, guessing card numbers, expiry dates and security codes. About 20,000 attempts a minute, every one from a different IP. Each merchant sees only a trickle. How does the network catch it, and what does it do?"
- Tiny amounts, spread across thousands of online merchants
- Card numbers walk through a few BIN ranges; most attempts are declined for a wrong expiry or code
- Every attempt comes from a different IP address
- The cards that pass will be used for real fraud later
Where can you see this, and what can you safely block?
No single merchant sees enough to notice. Where is the pattern visible, which counters reveal it, and how would you respond without blocking honest shoppers?
One strong answer
- The network's vantage point: only the network sees every merchant and every issuer, so an attack spread thin is visible in aggregate there.
- Count the right things: per-card velocity fails, because each guessed number is tried once or twice. Count instead per BIN range, per merchant and acquirer, and per device fingerprint, and watch the decline reasons (wrong expiry or security code) spike on card-not-present traffic.
- Respond narrowly: throttle or step up authentication (3-D Secure) for the affected BIN ranges on card-not-present traffic, flag the merchants receiving the burst, and leave card-present payments untouched.
- Close the loop: tell issuers which cards were approved during the attack, because those numbers are now known to work and should be monitored or reissued.
Final challenge · Quest check
Q1/4 Why is the network better placed than any single merchant to detect card testing?
Answer & explanation
Answer: B
The attack is designed to stay below every merchant's radar. Aggregating across the whole network turns thousands of trickles into one obvious stream.
Q2/4 Why do per-card velocity limits fail against card testing?
Answer & explanation
Answer: B
Velocity needs a key that the attacker reuses. Here that's the BIN range, the targeted merchants and the attacker's devices, not the individual card.
Q3/4 An acquirer times out and resends an authorization that actually succeeded. What prevents a double charge?
Answer & explanation
Answer: B
The idempotency key is the acquirer ID plus the original transaction ID. A repeat carries the same values, so the network answers from its response cache instead of authorizing twice.
Q4/4 At a 55,000 TPS peak, each authorization is a ~600-byte request and a ~600-byte response. Roughly what bandwidth is that?
Answer & explanation
Answer: A
55,000 × 600 bytes × 2 ≈ 66 MB/s, and × 8 bits ≈ 0.5 Gbps. Payment networks are latency and reliability problems far more than bandwidth problems.
Interview Conclusion#
"Excellent work. You've demonstrated strong understanding of payment systems, handled the scale requirements well, and made good trade-off decisions around consistency and availability. Any questions for me?"
"Thank you! I'd love to understand how Visa actually handles the transition when a card network launches a new data center. How do you migrate traffic without impacting transactions?"
"Great question. We typically..."
Summary: Concepts Applied from 10-Week Course#
Week 1: Data at scale
- Partitioning: BIN-based routing for O(1) issuer lookup
- Replication: Multi-DC sync/async for availability
- Hot Keys: Velocity tracking with sharded Redis
- WAL: Transaction durability before response
Week 2: Failure-first design
- Timeouts: Tiered timeout strategy for issuer calls
- Idempotency: Transaction IDs prevent double-charging
- Circuit Breakers: Data center health monitoring
- Stand-In: Graceful degradation when issuer unavailable
Week 3: Messaging & async
- Batch Processing: Clearing runs as nightly batch
- Transactional Outbox: Settlement file generation
- Event Sourcing: Transaction log as source of truth
Week 4: Caching
- In-Memory: BIN tables for sub-microsecond lookup
- Velocity Cache: Redis for fraud detection features
Week 5: Consistency
- Eventually Consistent: Cross-region replication
- Strong Consistency: Regional sync replication
- Conflict Resolution: Auth/clearing reconciliation
Scale
- 65,000+ messages/second tested network capacity (Visa)
- ~434 billion network-processed transactions per year (2025)
- ~$27 trillion in payment volume (2025, Visa FY2025 + Mastercard GDV)
- 200+ countries, 160+ currencies
Reliability
- 99.9999% uptime (32 seconds/year downtime; Visa's stated figure, Oct 2025)
- Zero tolerance for double-charging
- Global redundancy across 7 data centers (Visa, Oct 2025)
- Automatic failover in < 30 seconds (this design's target)
Speed
- < 2 second end-to-end authorization
- ~1 millisecond fraud scoring (Visa Advanced Authorization, 2019)
- < 200ms network processing (design target)
- Real-time across the globe
Security
- $40+ billion fraud prevented annually (Visa alone, Oct 2025)
- 500+ attributes analyzed per transaction (Visa Advanced Authorization, 2019)
- AI models trained on billions of transactions
- 22 billion security events monitored daily (Visa, Oct 2025)
Key lessons
- Latency is king - every millisecond matters at scale
- Availability > consistency for auth (reconcile later)
- In-memory processing for critical path
- Redundancy at every layer - backups have backups
- Stand-in processing: graceful degradation over failure
Sources#
Official Documentation:
- Docs
Visa Developer Portal - VisaNet Connect APIs: https://developer.visa.com/capabilities/visanet-connect-acceptance
- Read
Visa Corporate - Technology & Infrastructure: https://corporate.visa.com
- Docs
Mastercard Developer Documentation: https://developer.mastercard.com
- Docs
ISO 8583 Standard Specification: https://www.iso.org/standard/31628.html
Statistics and Data:
- Read
Visa FY2025 Form 10-K: https://www.sec.gov/Archives/edgar/data/1403161/000140316125000089/v-20250930.htm
- Read
Visa FY2025 Annual Report - Financials: https://annualreport.visa.com/financials/default.aspx
- Read
Mastercard Inc. Form 10-K 2025: https://www.sec.gov/Archives/edgar/data/1141391/000114139126000013/ma-20251231.htm
- Paper
Mastercard 4Q25 Earnings Release (operating performance tables): https://s25.q4cdn.com/479285134/files/doc_financials/2025/q4/4Q25-Mastercard-Earnings-Release.pdf
- Read
CoinLaw - Visa Statistics 2025: https://coinlaw.io/visa-statistics/
- Read
CoinLaw - Mastercard Statistics 2025: https://coinlaw.io/mastercard-statistics/
Architecture and Technical:
- Docs
Visa Perspectives - Inside Visa's Engine of Global Commerce: https://corporate.visa.com/en/sites/visa-perspectives/security-trust/inside-visa-global-commerce-engine.html
- Blog
Increase - Visa: Half a Century of High Availability: https://increase.com/articles/visa-redundancy
- Blog
Increase - ISO 8583: The Language of Credit Cards: https://increase.com/articles/iso-8583-the-language-of-credit-cards
- Blog
UniBul - How Visa's Payment System Works: https://blog.unibulmerchantservices.com/how-visas-payment-system-works/
- Docs
Network Computing - Inside Visa's Data Center: https://www.networkcomputing.com/data-center-networking/inside-visa-s-data-center
- Blog
The Globe and Mail - Inside Visa's data fortress (2012): https://www.theglobeandmail.com/technology/tech-news/inside-visas-data-fortress/article4216479/
- Paper
Visa - Data Centers infographic (2017): https://usa.visa.com/dam/VCOM/global/visa-everywhere/documents/visa-data-center-infographic.pdf
- Paper
Philadelphia Fed - Clearing and Settlement of Interbank Card Transactions: https://www.philadelphiafed.org/-/media/frbp/assets/consumer-finance/discussion-papers/d-2013-october-clearing-settlement.pdf
Security and Fraud:
- Read
Visa - AI-Powered Fraud Solutions: https://www.visaacceptance.com/en-us/solutions/ai-driven-fraud-management.html
- Read
Visa - Advanced Authorization: https://corporate.visa.com/en/solutions/secure-card-payments.html
- Read
Visa Newsroom - $25 Billion Fraud Prevented: https://usa.visa.com/about-visa/newsroom/press-releases.releaseId.16421.html
- Read
Visa - Generative AI Fraud Solution: https://investor.visa.com/news/news-details/2024/Visa-Announces-Generative-AI-Powered-Fraud-Solution-to-Combat-Account-Attacks/default.aspx
Payment Industry:
- Wiki
Wikipedia - ISO 8583: https://en.wikipedia.org/wiki/ISO_8583
- Wiki
Wikipedia - Four Corners Model: https://en.wikipedia.org/wiki/Four_Corners_Model_for_Payment_Security
- Blog
Cryptomathic - Four Corners Model: https://www.cryptomathic.com/blog/cardholder-merchant-issuer-acquirer-the-four-corners-model-for-payment-security-and-key-management
- Docs
IR Guide - Introduction to ISO 8583: https://www.ir.com/guides/introduction-to-iso-8583
Further Reading#
Official Documentation:
- Docs
Visa Developer Portal: https://developer.visa.com (Complete API documentation for VisaNet integration)
- Docs
Mastercard Developers: https://developer.mastercard.com (Payment gateway and processing APIs)
- Read
PCI Security Standards Council: https://www.pcisecuritystandards.org (Security compliance requirements)
Engineering Talks (Highly Recommended):
- Talk
Visa Technology - Various talks on VisaNet architecture at tech conferences
- Blog
Stripe Engineering - Payment infrastructure insights (complementary perspective)
Books:
- Book
"Designing Data-Intensive Applications" by Martin Kleppmann - Chapters on replication, partitioning, and consistency
- Book
"System Design Interview Vol 2" by Alex Xu - Payment system design patterns
- Book
"Building Microservices" by Sam Newman - Distributed systems patterns applicable to payment networks
Related Systems to Study:
- Read
SWIFT Network: International bank messaging (different scale, complementary)
- Side quest
India's UPI: Modern payment rail with different architecture (see Bonus Problem 1)
(opens in new tab) - Book
China's UnionPay: Largest card network by cards issued
- Read
RTP/FedNow: Real-time payment rails (newer, different model)
Self-Assessment Checklist#
After studying this design, you should be able to:
- Explain the four-party model and role of payment networks
- Design a system handling 65,000+ TPS with < 200ms latency
- Implement real-time fraud detection in < 1 millisecond
- Describe ISO 8583 message format and key fields
- Design multi-data-center architecture for 99.9999% uptime
- Explain authorization vs clearing vs settlement
- Calculate interchange fees and net settlement positions
- Implement idempotency for financial transactions
- Design stand-in processing for issuer unavailability
- Explain trade-offs between consistency and availability in payments
This case study walks through a likely design for a network at Visa's and Mastercard's scale: roughly $27 trillion a year (2025), fraud scored in about a millisecond, and the six-nines availability Visa states for its network.
this lesson in shorts · 3
play them all →One idea from this lesson in about a minute each, made for your phone.
- 0/7stages cleared
Follow-up drills
0 of 10 practisedYou presented Visa/Mastercard Global Payment Network. Now the interviewer pushes back. Answer each follow-up out loud or in the box, then reveal a strong answer.
“Take 434 billion transactions a year down to a per-second number. What capacity do you actually build, and is bandwidth the problem?”
Pushes on · deriving peak from yearly volume and naming the real bottleneck
Go deeper · reading trail
all reading →Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.
Ledger: Stripe's system for tracking and validating money movement
Ilya Ganelin · 2024- What
- Stripe's immutable, auditable log of every money movement.
- Key idea
- Ledger records double-entry style balance changes for every payment step and continuously checks that funds flows close out, flagging anything unexplained.
- Why here
- The clearing and settlement side of this quest: authorisation is fast, but the money trail must reconcile exactly afterwards.
VisaNet
- What
- Visa's own overview of VisaNet, the network that authorises card transactions.
- Key idea
- Banks and partners connect to VisaNet to authorise transactions, merchants reach it through them, and Visa's 'six nines' target allows under a second of downtime a day.
- Why here
- The four-party model and availability target this quest designs for, in the network operator's words.
Building PhonePe's Real-Time Transaction Model to prevent Frauds: A Journey
Nipun VK · 2022- What
- How PhonePe's Trust & Safety team built a model that declines fraudulent peer-to-peer payments as they happen.
- Key idea
- Fraud reports are noisy and mixed with disputes, so they segmented reported cases with demographic, behavioural and transaction signals before training a real-time model.
- Why here
- The fraud engine in this quest's authorisation path: the decision must be made in-line, from features available in milliseconds.
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.


