ai_ml / ml system design / 03_design_fraud_detection.md

Design: a fraud detection system

6 interview angles 5 min read source

Design: a fraud detection system

The canonical classical-ML system design. It exercises imbalance, cost-sensitive thresholds, real-time features, label delay and adversarial drift — most of the hard parts in one problem.

Clarify

  • Decision: block, allow, or send to manual review. Three outcomes, not two.
  • Scale: 10k transactions/second peak.
  • Latency: synchronous, inside the payment flow — under 100ms.
  • Base rate: ~0.1% fraudulent.
  • Costs: a missed fraud averages $500; a false positive costs a review (~$5) and customer friction.
  • Labels: chargebacks arrive 30-90 days later. Manual reviews give faster partial labels.

The cost asymmetry (100:1) and the label delay are the two facts that drive everything downstream.

Frame it

Binary classification producing a score, not a decision. The score maps to three actions by two thresholds:

score < 0.3          -> allow
0.3 <= score < 0.85  -> manual review queue
score >= 0.85        -> block

Thresholds come from the cost matrix and the review team’s capacity, not from F1. If the team can review 2,000 cases/day, that caps the middle band regardless of what the model says. See ../04_model_evaluation/02_precision_recall_f1.md.

Metric: PR-AUC, not ROC-AUC. At 0.1% positives the true-negative pool is enormous, so a large increase in false positives barely moves FPR while precision collapses. See ../04_model_evaluation/03_roc_auc_vs_pr_auc.md.

Architecture

transaction -> feature service ──> model ──> score ──> decision
                    │                                     │
        ┌───────────┴────────────┐                        v
   online store            streaming aggregates      allow / review / block
   (Redis: profile,        (Flink: velocity                    │
    card, merchant)         counters, last 5m)                 v
                                                        review outcomes
                                                        (fast labels)

Features

Grouped by how they’re computed, because that determines the infrastructure:

Type Examples Source
Transaction amount, currency, merchant category, hour the request itself
Profile account age, historical average, usual countries online store, updated in batch
Velocity transactions in last 5m/1h/24h, distinct cards streaming aggregates
Graph shared device/IP across accounts precomputed batch
Derived amount / user’s historical mean, distance from last transaction computed at request

Velocity features are the most predictive and the hardest to serve. “Five transactions in the last two minutes” needs a streaming pipeline (Kafka plus Flink) writing counters to the online store — you cannot compute it from a warehouse at request time.

Point-in-time correctness is essential. Training rows must use feature values as of that transaction’s timestamp. Joining current profile state onto historical transactions leaks the outcome backwards. This is where a feature store earns its cost. See ../03_feature_engineering/06_feature_stores.md.

Model

Gradient boosting — LightGBM or XGBoost. It wins on tabular data, trains fast enough for frequent retraining, handles missing values natively, and inference is well under the latency budget. See ../02_classical_ml/05_gradient_boosting.md.

Handle imbalance with scale_pos_weight plus threshold tuning, not SMOTE. Note that re-weighting distorts probabilities, so recalibrate if you use the score in an expected-loss calculation. See ../03_feature_engineering/05_imbalanced_data.md.

Keep a rules layer alongside the model. Rules encode known patterns instantly, are explainable to compliance, and cover the model’s blind spots on brand-new attack patterns:

if velocity_5m > 10 or amount > user.p99_amount * 10:
    return BLOCK                       # rules fire first, deterministic
return model_decision(score)

Rules-plus-model is the production reality, and proposing only a model is a gap.

Latency

100ms budget, roughly:

feature fetch (online store, parallel)   ~20ms
derived features                          ~5ms
model inference                          ~10ms
rules + decision                          ~5ms
logging (async, off the path)              0ms
                                     ---------
                                        ~40ms   comfortable

Fetch features in parallel, not sequentially. Have a fallback: if the feature store times out, score on transaction-only features and lean on rules. A payment flow cannot hang because the model is unavailable — degraded is better than down.

The label delay problem

Chargebacks take 30-90 days. Consequences:

  • You cannot evaluate today’s model on today’s data. Accept a 30-90 day lag on the true metric.
  • Manual review outcomes are fast partial labels — biased toward the reviewed band, but timely. Weight accordingly.
  • Monitor proxies meanwhile: score distribution, feature drift, block rate, review-queue depth. See ../15_mlops_llmops/02_monitoring_and_drift.md.

Adversarial drift

Unlike most ML problems, the data-generating process is an adversary who adapts to your model.

  • Retrain frequently — weekly or faster.
  • Expect concept drift by default, not as an anomaly.
  • Don’t expose scores or reasons to users; that’s a feedback channel for attackers.
  • Keep a rules layer for rapid response — a new attack pattern can be blocked in an hour, retraining takes days.
  • Watch for blocked-transaction blind spots: you never learn whether blocked transactions were actually fraud. Consider letting a tiny random sample through to maintain an unbiased signal, if the business tolerates it.

That last point is a genuine feedback-loop problem and a strong thing to raise.

Interview angle

  • “Design a fraud detection system.” — score-not-decide with two thresholds mapping to allow/review/block, gradient boosting on transaction, profile, velocity and graph features, streaming aggregates for velocity, sub-100ms serving with a degraded fallback, and a rules layer alongside the model.
  • “Why PR-AUC rather than ROC-AUC?” — at 0.1% positives the true-negative pool is huge, so false positives barely move FPR while precision collapses. PR-AUC has no true-negative term and reflects what reviewers experience.
  • “Where do the thresholds come from?” — the cost matrix and review capacity. A 100:1 cost ratio and a team that can review 2,000 cases a day determine the bands, not a symmetric metric.
  • “Which features are hardest to serve, and why?” — velocity features. “Five transactions in two minutes” requires a streaming pipeline writing to an online store; you can’t compute it from the warehouse inside a 100ms budget.
  • “Labels arrive 60 days late. How do you know the model still works?” — you don’t, directly. Use manual review outcomes as faster biased labels, and monitor proxies: score distribution, feature drift, block rate, queue depth. Accept the lag on the true metric.
  • “What’s different about fraud versus other ML problems?” — the data-generating process is adversarial and adapts to you. Concept drift is the norm, retraining is frequent, a rules layer gives you hour-scale response, and blocking creates a blind spot where you never learn the counterfactual.