?

Side quest 04Card payments

Visa/Mastercard Global Payment Network.

The Invisible Infrastructure Moving ~$27 Trillion Annually

INTERMEDIATE45 MIN READ6 PHASES · 45 MIN INTERVIEW+120 XP

Mission brief

Design a global card payment network

Quest log0/7 stages · 0/5 checkpoints · 0/7 quick checks
transactions a year processed on VisaNet and Mastercard's network (2025)
~434B
messages per second of tested capacity (Visa, Oct 2025)
65,000+
for the network's share of a < 2 s authorization (design target)
< 200 ms
for Visa's fraud score over 500+ risk attributes (Visa Advanced Authorization, 2019)
~1 ms

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 14-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases · 45 min

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.

Intro · 3 min

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.

Card authorization · card → merchant → acquirer → network → issuer and back, with stand-in when the issuer is down
LIVECard authorization · card → merchant → acquirer → network → issuer and back, with stand-in when the issuer is down
The Visa/Mastercard scale (2025)
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.

ExplainerThe whole quest: Visa and Mastercard

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.

Interviewer

"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:

Design a Global Payment Network

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
Interviewer

"Take a few minutes to think about this, then walk me through your approach. We have about 45 minutes."

Phase 1 · Stage 1 of 7 · 5 min budget · 2 min read

Phase 1: Requirements Clarification (5 minutes)#

Before diving in, you take a breath and start asking questions. This is crucial — never assume.

Your Questions#

Transcript
You

"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?"

Interviewer

"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."

You

"So the network doesn't hold funds or issue cards — it's purely a message routing and processing layer?"

Interviewer

"Exactly. We route authorization requests, handle clearing and settlement, and provide value-added services like fraud detection. The actual money movement happens between banks."

You

"What's the latency budget for authorization? And how is it distributed?"

Interviewer

"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."

You

"For fraud detection, how much latency can we add to the critical path?"

Interviewer

"It must be inline with authorization — no more than 1-2 milliseconds. We can't slow down legitimate transactions."

You

"What happens if the issuing bank is unreachable during authorization?"

Interviewer

"Good question. We need 'stand-in processing' — the ability to make authorization decisions on behalf of an unreachable issuer within pre-agreed parameters."

You

"One more — what's the settlement timeline?"

Interviewer

"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#

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#

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 · Stage 2 of 7 · 5 min budget · 2 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"Let me work through the numbers to understand the scale we're dealing with."

Your turn · checkpoint 1not answered

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?

Think it through, or jot it down. Then compare.

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#

WorksheetTransaction volume
  • 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#

WorksheetISO 8583 message size
  • 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#

WorksheetStorage for transaction records
  • 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#

Estimation 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)
Quick check3 questions · +5 XP each

Requirements and estimation

  1. 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.

  2. 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.

  3. 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 · Stage 3 of 7 · 10 min budget · 4 min read

Phase 3: High-Level Design (10 minutes)#

You

"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."

Your turn · checkpoint 2not answered

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?

Think it through, or jot it down. Then compare.

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#

DiagramThe 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 service

System Architecture#

DiagramHigh-level 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 service

Component Breakdown#

You

"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#

You

"Let me trace through a typical authorization flow..."

DiagramAuthorization flow (< 2 seconds end-to-end) · steps 1–3
%% 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
DiagramAuthorization flow (< 2 seconds end-to-end) · steps 4–7
%% 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
Quick check3 questions · +5 XP each

High-level design

  1. 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.

  2. 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.

  3. 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 · Stage 4 of 7 · 20 min budget · 24 min read

Phase 4: Deep Dives (20 minutes)#

Interviewer

"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)#

You

"This is the heart of the system. Let me explain how we achieve sub-200ms latency at 65,000 TPS."

The Problem#

Authorization latency challenge

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#

DiagramLow-latency authorization path
%% 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 -.-> AP2

Message 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#

pythonAuthDecision · 260 lines
# 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."""
        pass

Edge Cases#

Interviewer

"What happens if the issuer is slow or unreachable?"

Your turn · checkpoint 3not answered

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?

Think it through, or jot it down. Then compare.

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.

You

"We implement stand-in processing with tiered timeouts..."

Scenario: Issuer taking too long or unreachable

Timeout Strategy (Week 2, Day 1):

DiagramIssuer timeout handling — tiered timeouts
%% 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
ExplainerCard authorization and stand-in processing

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)#

Interviewer

"How do you score 500+ attributes for fraud in under a millisecond?"

Your turn · checkpoint 4not answered

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?

Think it through, or jot it down. Then compare.

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.
You

"This is where AI meets extreme performance engineering. Let me show you the architecture..."

The Solution: ML at Millisecond Scale#

DiagramFraud detection architecture
%% 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)"]:::bad

Implementation#

pythonFraudFeatures · 289 lines
# 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."""
        pass
Quick check3 questions · +5 XP each

Authorization and fraud scoring

  1. 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 authorize is the idempotency check. A repeat transaction_id gets 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.

  2. 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.

  3. 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)#

Transcript
Interviewer

"How do you achieve 99.9999% uptime across global data centers?"

You

"This requires a sophisticated multi-data-center architecture with synchronous and asynchronous replication..."

The Architecture#

DiagramGlobal data center topology
%% 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 external

REPLICATION 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#

pythonDataCenterStatus · 202 lines
# 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.OFFLINE

Physical Resilience#

Physical data center resilience (Visa Ashburn example, as publicly reported 2012-2025)

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)#

Transcript
Interviewer

"Walk me through how money actually moves after authorization."

You

"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#

Clearing and settlement timeline
  • 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#

Interchange fee example

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#

pythonClearingRecord · 317 lines
# 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
        pass
Quick check3 questions · +5 XP each

Data centers and clearing

  1. 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.

  2. 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.

  3. 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 · Stage 5 of 7 · 5 min budget · 2 min read

Phase 5: Scaling and Edge Cases (5 minutes)#

Interviewer

"How would this system scale to 10x the current load?"

Scaling Strategy#

You

"The system is designed for horizontal scaling at multiple layers..."

Scaling to 10X (650,000 TPS)
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#

Interviewer

"What are some edge cases we should handle?"

Edge Case 1: Duplicate Authorization Requests#

Scenario: Acquirer retries due to timeout, but original succeeded
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#

Scenario: Clearing amount differs from authorization
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#

Scenario: Black Friday, issuer can't keep up
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
Quick check3 questions · +5 XP each

Scaling and edge cases

  1. 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.

  2. 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.

  3. 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 · Stage 6 of 7 · 2 min

Phase 6: Monitoring and Operations#

Interviewer

"How would you monitor this system in production?"

Key Metrics#

Monitoring dashboard
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#

Runbook: Authorization Latency Spike
Symptoms
  • p99 latency > 200ms
  • Customer complaints about slow checkout
  • Acquirer timeout rates increasing
Diagnosis
  1. Check issuer latency breakdown: $ auth-latency-breakdown --last 5m
  2. Identify slow issuers: $ issuer-latency-report --threshold 500ms
  3. Check fraud scoring latency: $ fraud-latency-percentiles --last 5m
  4. Check DC health: $ dc-health-status --all
Resolution

If single issuer slow:

  1. Enable stand-in for that issuer
  2. Alert issuer operations team
  3. Monitor stand-in approval quality

If fraud scoring slow:

  1. Check model serving cluster health
  2. Scale up GPU nodes if needed
  3. Consider bypassing fraud for low-risk

If DC overloaded:

  1. Shift traffic to backup DC
  2. Scale up compute in affected DC
  3. Investigate traffic spike cause
Escalation
  • Network Operations Center (NOC)
  • Risk Operations Center (ROC) if fraud-related
  • Issuer relations if bank-specific
Quick check3 questions · +5 XP each

Running the network

  1. 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.

  2. 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.

  3. 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 · Stage 7 of 7 · 2 min

Final Challenge: The Card-Testing Bot#

The interviewer pulls up one more slide. "This one happens every week somewhere."

Interviewer

"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?"

The attack brief
  • 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
Your turn · checkpoint 5not answered

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?

Think it through, or jot it down. Then compare.

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.
Quick check4 questions · +5 XP each

Final challenge · Quest check

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Wrap-up · 4 min

Interview Conclusion#

Transcript
Interviewer

"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?"

You

"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?"

Interviewer

"Great question. We typically..."

Summary: Concepts Applied from 10-Week Course#

Concepts from 10-week course in Visa/Mastercard design
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
Why Visa/Mastercard is an engineering marvel
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
  1. Latency is king - every millisecond matters at scale
  2. Availability > consistency for auth (reconcile later)
  3. In-memory processing for critical path
  4. Redundancy at every layer - backups have backups
  5. Stand-in processing: graceful degradation over failure

Sources#

Official Documentation:

Statistics and Data:

Architecture and Technical:

Security and Fraud:

Payment Industry:

Further Reading#

Official Documentation:

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:

Self-Assessment Checklist#

After studying this design, you should be able to:

After studying this design, you should be able to0/10 ready
  • 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.

End of Side quest 04 · Visa/Mastercard Global Payment Network
Clear the questFinish Visa / Mastercard.Stages clear as you read them. Marking complete unlocks after ~8 more min of reading.
  • 0/7stages cleared

Follow-up drills

0 of 10 practised

You 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.

Follow-up 1 / 10 · scale & numbers

“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

Revealing marks it practised.
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

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.

StripeBlog post · ~14 min

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.
taught here: Clearing and settlement
VisaDocs · ~3 min

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.
taught here: Authorization network
PhonePeBlog post · ~6 min

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.
taught here: Real-time fraud detection

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.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…