Vibe Engines
YouTube
System Design

Design a Fraud Detection System

Step 1 / 9

Learn system design by building a real-time fraud detection system like Stripe Radar step by step.

The numbers to beat~80msdecision budgetallow/deny/step-up3 outcomesinlinesynchronous in checkout

The whole design, in writing

Learn system design by building a real-time fraud detection system like Stripe Radar step by step. An interactive guide covering why static rules fail, real-time feature computation (velocity, device, geo) from a feature store, ML risk scoring blended with rules, streaming aggregations under a tight latency budget, the label feedback loop that retrains models, allow/deny/step-up decisioning with graph features, and the unhappy paths (concept drift, false positives, class imbalance, explainability).

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is fraud detection?

A stolen card is being tested on your checkout right now. You have maybe 80 milliseconds to decide: let it through and eat a chargeback, or block it — and if you block a legitimate customer, you've lost a sale and annoyed a real person. Do this millions of times an hour, against attackers who adapt the moment you catch them.

Transactionauthorize?
New in this step: Transaction.

A fraud detection system scores every transaction's risk in real time and decides allow / deny / challenge within a tight latency budget. The design blends fast rules with an adaptive ML model, fed by real-time features (velocity, device, geo), and closes a feedback loop so it keeps learning as fraud evolves.

What the new pieces do

Transactionclient
A payment or signup arriving for a real-time risk decision — allow, block, or challenge — in under ~100ms.

Step 1 · Why not just rules?

Static rules break

The first instinct: hardcode rules. "Block if amount > $2000 AND country ≠ home." It catches something on day one. Why does a rules-only system fail over time?

Transactionauthorize?
The system as it stands at this step.

A hand-written rule set for fraud. Why does it fail over time?

  1. Rules are actually fast and explainable — that's their strength. Their weakness is that they're static and gameable: fraudsters learn the thresholds and slip under them, while legitimate edge cases trip false positives.

  2. Humans write rules easily; the problem is they can't enumerate every evolving fraud pattern, and fixed thresholds are both easy to evade and blunt (blocking real customers). You need something adaptive that learns subtle patterns.

  3. Fraudsters probe until they find the threshold and stay just under it; meanwhile rigid limits block legitimate outliers (a real customer's big foreign purchase). Rules can't capture subtle, shifting, multi-signal patterns — that needs learning from data.

Rules alone are static and gameable: attackers probe until they find a threshold and stay just under it, while blunt limits false-positive on legitimate outliers. Fraud is a moving, subtle, multi-signal target — you can't enumerate it by hand. You need a system that learns patterns from data and adapts, while keeping rules for the clear-cut and compliance cases.

Why this piece earns its place

Keeping rules for the clear-cut cases is right, but notice what it commits you to operationally: a rule set only ever grows. Every incident review ends with a new rule, and none of them is ever deleted, because nobody can prove which ones are still earning their place. Far enough in, you have hundreds of overlapping conditions, an unknown share of your declines come from a rule written for an attack that ended long ago, and any measurement of the model is confounded by whatever the rules already removed from its traffic. So treat the rule set as code with an expiry. Attribute every hard block to the specific rule that fired and log it with the decision. Run each rule in shadow alongside its live form, so you can read what it would block if it were switched off and how much of that the model would have caught anyway. Then retire on a schedule: a rule that hasn’t fired in a long time, or whose blocks are now fully covered by the score, comes out. Otherwise the engine you kept for the obvious cases quietly becomes the thing nobody dares change.

Step 2 · The decision path

Score, then decide, fast

Put a decision service in the payment path. It has a hard constraint most systems don't: it must return a verdict in tens of milliseconds, synchronously, or it delays every checkout. What must it produce?

Decision Servicescore · decideRules Enginehard blocks
New in this step: Decision Service, Rules Engine.

The decision service takes a transaction and returns a verdict — allow, deny, or step-up (challenge with 2FA/review) — within a strict latency budget. First pass: run fast rules (hard blocks, allowlists, compliance). Then compute a risk score and threshold it into a decision. It sits inline with the payment, so everything it does must be fast and reliable.

Why this piece earns its place

Putting this service inline makes its availability the payment path’s availability, so the question this step creates is what a timeout returns. There has to be a default verdict, and it has to be allow. Denying on error turns a slow dependency into an incident that looks, to every customer at once, like their card being refused — and the fraud that slips through that window costs far less than the checkout you just broke. Two things make that safe enough to say out loud. The timeout is per call, not per request: features, rules and the scorer each get their own slice of the budget, so a slow model returns a rules-only verdict instead of eating the whole window and returning nothing. And the cheap deterministic checks — blocklists, sanctions, hard compliance blocks — stay on the fail-open path, because they cost almost nothing and they are the ones you can’t afford to skip. Then alarm on the fallback rate itself. An attacker who can make you slow can make you blind, so a rising rate of timed-out decisions is an attack signal, not an infrastructure footnote.

  • ~80msdecision budget
  • allow/deny/step-up3 outcomes
  • inlinesynchronous in checkout

What the new pieces do

Decision Servicegateway
Orchestrates the decision: gather features, run rules + model, and return allow / deny / step-up within the latency budget.
Rules Engineservice
Deterministic rules for the obvious cases (blocklists, impossible velocity) and compliance — fast, explainable, editable.

Step 3 · What the model sees

Real-time features

A single transaction — "$40, card X, IP Y" — barely tells you anything. The signal is in context: how this compares to card X's history and behavior right now. How do you get rich features within the latency budget?

Feature Computevelocity · geoFeature Storeonline features
New in this step: Feature Compute, Feature Store.

A lone transaction is weak signal. Where does the fraud signal actually come from?

  1. The signal is in context: how many transactions this card made in the last minute/hour (velocity), is this a new device, how far from the last location, account age, prior chargebacks. These are precomputed and streamed into a low-latency feature store so scoring reads them in milliseconds.

  2. Amount and country alone are the weak, gameable signals rules already use. Real detection needs behavioral context — velocity and history — which a lone request doesn't contain.

  3. Scanning a card's full history per request blows the latency budget. Features must be precomputed and maintained incrementally in a feature store, ready to read in milliseconds.

Compute contextual features and serve them from a low-latency feature store. The powerful signals are behavioral: velocity (transactions per card/device/IP in the last minute/hour/day), device fingerprint and whether it's new, geo distance from the last transaction, account age, and prior chargebacks. These are precomputed and updated in near-real-time, so the scorer reads them in milliseconds instead of recomputing history per request.

Why this piece earns its place

The trap here is that one feature now has two implementations — the pipeline that writes it for scoring, and whatever the training job computes when it rebuilds history from the warehouse. The day those disagree, nothing fails. Offline evaluation looks excellent, because the training job quietly held information the serving path never has: a window that includes the transaction being scored, a chargeback flag that was only set weeks afterwards, a geo distance measured against a location not yet recorded at decision time. The model learns to lean on signal that doesn’t exist in production, and its live precision comes in worse than every test promised. The fix is to stop reconstructing training data at all. Log the exact feature vector the scorer read, with its timestamp, the model version and the verdict it produced, and train on those logged vectors. It’s strictly less convenient — you can only train on features you were already computing, so a new feature costs a waiting period before it has any history — and it’s the only form of point-in-time correctness that can’t drift, because there is no second implementation left to disagree with.

  • velocity#txns / window
  • device + geonew? far?
  • feature storems-latency reads

What the new pieces do

Feature Computeservice
Assembles the signals a decision needs: transaction velocity, device fingerprint, geo distance, account age, history.
Feature Storecache
Low-latency store of precomputed + streaming features, so scoring reads them in milliseconds instead of recomputing.

Step 4 · Score the subtle stuff

The ML model

Rules caught the obvious; features are ready. Now you need to turn dozens of signals into a single risk number that captures patterns no human wrote down — combinations like "new device + unusual amount + high velocity + far geo."

Decision Servicerules + modelRules Enginehard blocksML Scorerrisk score
New in this step: ML Scorer.

Run an ML model (often gradient-boosted trees for tabular fraud — fast and strong) that takes the features and outputs a fraud probability. Combine it with rules: a hard rule can veto, the model score handles the nuanced middle, and a threshold (tuned to your fraud-vs-friction tradeoff) maps the score to allow / step-up / deny. Keep the model fast (the latency budget) and monitored (fraud drifts, so a stale model quietly rots).

Why this piece earns its place

A scorer with dozens of features has dozens of ways to be handed nothing: a store read that spends its slice of the budget and returns late, a brand-new card with no history to aggregate, a device fingerprint that fails to resolve. What you do with the gap is a modelling decision, not plumbing. The tempting fix is to fill in a mean or a zero at serving time, and it’s the wrong one, because a mean says average and average is a confident statement. A first-ever transaction on a fresh account then scores like a typical customer — precisely the case you most wanted to look unusual. Instead, train missingness in as its own value: gradient-boosted trees handle an absent feature natively, sending it down its own branch, and you generate that absence during training at something like the rate production produces it so the model has genuinely seen it. This matters most in bulk. When the feature store degrades, nulls don’t arrive one at a time — every request loses the same signals at once, and a model that reads absence as average keeps returning comfortable scores through an entire incident.

  • P(fraud)model output
  • thresholdtuned tradeoff
  • rules veto+ model middle

What the new pieces do

ML Scorerdata
A trained model that scores subtle fraud patterns rules can't express, returning a probability of fraud.

Step 5 · Keep features fresh

Streaming aggregations

Velocity features — "10 transactions on this card in the last 60 seconds" — are only useful if they reflect what happened seconds ago, including the transaction being scored. A nightly batch job is far too slow to catch a burst attack in progress. How do you keep them current?

Event Streamreal-time aggFeature Computevelocity · geoFeature Storeonline features
New in this step: Event Stream.

Stream every event through a real-time processing layer (Kafka + Flink/streaming jobs) that maintains windowed aggregations — counts and sums per card/device/IP over sliding time windows — and writes them straight into the online feature store. So the moment a fraudster fires 50 rapid transactions, the velocity feature spikes and the very next decision sees it. Fresh features are what catch attacks while they happen.

Why this piece earns its place

Counting gets harder once the counter is a decision input. The stream delivers at least once, so a retried authorization, a consumer rebalance or a replay after an outage all recount the same event and the velocity feature climbs with nothing having happened. That isn’t a dented metric; it’s a customer whose own retry pushed them past a limit and got them declined. So the aggregation deduplicates on the event’s own id rather than counting arrivals, and windows are keyed by event time with a bounded allowance for late arrivals — undercount and you miss a burst, overcount and you decline someone real. The second thing worth saying out loud is that this pipeline can never see the transaction it is about to score: the event is published, travels through the aggregation, and lands in the store long after the verdict was due. So the decision service folds the in-flight transaction into the count locally before it compares against anything, and the stream supplies everything up to it. Otherwise the first transaction of a burst is always judged against a window that doesn’t contain it.

  • sliding windows1m / 1h / 1d
  • secondsfeature freshness
  • catch in flightburst attacks spike velocity

What the new pieces do

Event Streambus
Every event flows here so real-time aggregations (velocity, counts per card/device) stay fresh for the next decision.

Step 6 · Learn from the outcome

The label feedback loop

The model was trained on yesterday's fraud, but fraud evolves — a model left alone silently decays. To keep learning, you need ground truth: which transactions actually turned out fraudulent. But that truth arrives late (a chargeback can take weeks). How do you close the loop?

Feature Storeonline featuresRules Enginehard blocksML Scorerrisk scoreLabelschargebacks
New in this step: Labels.

Collect labels from every source of truth — confirmed chargebacks, analyst verdicts from the review queue, customer reports — and feed them back to retrain the model regularly on fresh fraud patterns. Handle the label delay (chargebacks lag) by using faster proxy signals where possible and being deliberate about training windows. Continuously monitor live metrics (approval rate, fraud rate, score distribution) to catch drift before the slow labels confirm it.

Why this piece earns its place

There’s a bias baked into this loop that no amount of retraining repairs: you only ever learn the outcome of a transaction you allowed. Everything the system denied has no label, because nothing could happen to it — no chargeback, no complaint, no confirmation either way. So each retrain is fitted to the population the previous model approved, and the model grows steadily more confident about a region of the space it stopped sampling. A boundary that was slightly too aggressive on some legitimate segment gets encoded as fact, and the evidence that would contradict it can no longer be produced. The defense is to buy the missing labels deliberately: let a small randomized slice of transactions near the boundary through despite their score, and treat what happens to them as the unbiased sample the rest of your data isn’t. That costs real fraud losses, so you cap it by amount and keep it away from the clearly-bad tail. But it’s the only measurement that can tell you whether the boundary is still in the right place, and declining to pay for it just means never finding out.

  • chargebacksground truth (weeks late)
  • retrainon fresh fraud
  • monitordrift before labels

What the new pieces do

Labelsdata
Ground truth — confirmed fraud, chargebacks, analyst verdicts — fed back to retrain the model as fraud evolves.

Step 7 · The verdict & the network

Decisioning, step-up & graphs

A binary allow/deny is crude — the borderline cases deserve a middle path — and fraud is rarely a lone transaction: it's rings of linked accounts, shared devices, and reused cards. How do you decide well and see the network?

Decision ServiceFeature StoreRules EngineML ScorerReview QueueLabels
New in this step: Review Queue. · swipe to pan the diagram

The model scores a transaction at 0.52 fraud probability, squarely in the uncertain middle. What should the decision be?

  1. A hard binary cutoff treats a coin-flip-confidence score with the same certainty as a 0.99 or a 0.01 — you either eat the fraud or needlessly block a real customer on a case the model itself is unsure about.

  2. The uncertain middle is exactly where added friction pays off: a real customer clears 2FA in seconds while a fraudster often cannot, and the outcome becomes a high-quality label on a case the model was genuinely unsure about.

  3. That converts every ambiguous legitimate transaction into a lost sale, which is far more costly than a brief step-up challenge — deny-by-default is safe for the business but hostile to real customers.

Use a three-way decision: auto-allow the clearly-good, hard-deny the clearly-bad, and step up the uncertain middle (2FA challenge or a human review queue) — turning risk into friction only where it's warranted, and generating labels. Add graph features: link accounts by shared device/card/IP/address to spot fraud rings a per-transaction view misses (one flagged account taints its cluster). Reviews and challenges feed back as high-quality labels.

Why this piece earns its place

Step-up reads like a free middle path and it isn’t — it’s a rate you are spending. Some share of challenged customers abandon instead of completing, the channel carrying the challenge has its own cost and its own delivery failures, and all of that lands on the population you were unsure about, which skews genuine. So the number to manage isn’t really the threshold, it’s the fraction of traffic you’re willing to challenge: fix that as a budget, rank by score, and let the thresholds float to fill it. A noisy day then spends the same friction on the riskiest cases instead of quietly challenging far more people. Be careful, too, about what you conclude from a pass. A completed challenge is a good signal, not a clean one — challenges get defeated, and an attacker who also controls the phone clears it — so treating every pass as confirmed-legitimate feeds the retraining loop the one label an adversary can manufacture on demand. Weight it below a chargeback, and watch for accounts that clear challenges suspiciously often.

  • allow / step-up / deny3-way
  • graph featurescatch rings
  • reviews→ labels

What the new pieces do

Review Queuedata
Borderline cases routed to step-up auth (2FA) or human analysts, whose verdicts become high-quality labels.

Step 8 · The sharp edges

Drift, false positives & imbalance

Fraud detection has vicious edges: an adversary actively adapts (concept drift), false positives cost real revenue and trust, fraud is a tiny fraction of traffic (class imbalance), and you may have to explain a decline to a regulator.

TransactionEvent StreamDecision ServiceFeature ComputeFeature StoreRules EngineML ScorerReview QueueLabels
The system as it stands at this step. · swipe to pan the diagram

Fight drift with frequent retraining, drift monitoring, and champion/challenger model tests. Take false positives seriously — measure the cost of blocking good customers, and prefer step-up over hard deny near the boundary. Handle class imbalance (fraud is <1%) with proper metrics (precision/recall, PR-AUC — never raw accuracy), resampling, and cost-sensitive training. Ensure explainability (reason codes per decision) for disputes and compliance, and defend the pipeline from adversarial probing and gaming.

Why this piece earns its place

Explainability is the requirement that quietly arms the adversary. A reason code exists so a customer can be told why and a regulator can be answered — and it is also a free read of your decision boundary. Tell someone their decline was driven by a new device and high velocity and you have named the two things to change before the next attempt. Attempts are not independent events; a run of declines from the same actor is someone reading your responses one at a time. So split the artifact in two. Externally, a small fixed vocabulary of coarse, stable categories, chosen so each code covers a wide range of underlying causes and so the wording doesn’t shift every time the model is retrained. Internally, the full decision record — model version, the feature vector that was read, which rules fired, the score and the threshold in force — kept for disputes, audits and the next retrain. And treat the probing itself as signal rather than noise: the sequence of attempts on a card or device belongs in the features, so a patient attacker walking the boundary looks less safe with each try, not more.

You did it

You just designed a fraud detection system.

TransactionEvent StreamDecision ServiceFeature ComputeFeature StoreRules EngineML ScorerReview QueueLabels
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Static rules are gameable and blunt — fraud is adaptive and subtle, so it needs learning.
  • A decision service returns allow/deny/step-up inline, under a tight (~80ms) latency budget.
  • Real-time contextual features (velocity, device, geo, history) from a feature store carry the signal.
  • An ML model scores the subtle patterns; rules veto the obvious; a tuned threshold decides.
  • Streaming aggregations keep velocity features fresh enough to catch attacks in flight.
  • A label feedback loop (chargebacks, reviews) retrains the model as fraud evolves.
  • Three-way decisioning + graph features + reason codes handle nuance, rings, and compliance.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why not just use a rules engine for fraud?

    Rules are fast, explainable, and genuinely valuable for the clear-cut cases — blocklisted cards, impossible velocity, legal/compliance blocks — so you keep them. But a rules-only system fails over time for two structural reasons. First, it's static against an adaptive adversary: fraudsters probe your system until they infer the thresholds and then operate just under them, so any fixed rule decays the moment it's discovered. Second, hand-written thresholds are blunt, so they simultaneously miss subtle multi-signal fraud (patterns no human enumerated, like new-device plus unusual-amount plus high-velocity plus far-geo together) and false-positive on legitimate outliers (a real customer's large foreign purchase). Fraud is a shifting, high-dimensional, subtle target, which is exactly what machine learning is good at capturing from data — so the real system is a hybrid: rules for the obvious and for compliance, an ML model for the evolving, subtle majority, and a feedback loop so it keeps adapting.

  2. Why is the feature store and real-time feature freshness so central?

    Because a single transaction in isolation carries almost no fraud signal — "$40, card X, IP Y" looks fine — and the signal lives entirely in context: how this transaction compares to the card's recent behavior and history right now. The most predictive features are behavioral and time-sensitive: velocity (how many transactions this card/device/IP made in the last minute, hour, and day), whether the device is new, how far the geo is from the last transaction, account age, and prior chargebacks. Two things follow. First, you can't recompute this history per request within an ~80ms budget, so features must be precomputed and served from a low-latency online feature store. Second, the decisive velocity features must be fresh to seconds — a fraudster firing 50 rapid transactions is only catchable if the velocity feature reflects those transactions before the next decision — which is why events are streamed through real-time aggregation jobs that update the feature store continuously rather than in a nightly batch. If the feature store stalls, the scorer loses exactly these signals and detection collapses to the weak per-request info, which is why it's the heart of the system.

  3. How do rules and the ML model combine in one decision?

    They play complementary roles under a single decision service with a strict latency budget. Rules run first and handle the deterministic extremes: hard blocks (blocklists, impossible velocity, sanctioned entities), allowlists for known-good, and any compliance-mandated logic — fast, fully explainable, and instantly editable. What rules can't express is the nuanced middle, so the ML model takes the assembled features and outputs a fraud probability that captures subtle, learned combinations of signals. The two are blended: a hard rule can veto outright, while for everything else the model score drives the outcome through business-tuned thresholds that map the probability to a three-way decision — auto-allow below one threshold, hard-deny above another, and step-up (2FA or human review) in the uncertain band. This hybrid also degrades gracefully: if the model is unavailable, rules alone still block blatant fraud, so the payment path stays up with reduced accuracy rather than failing. The model finds the patterns; the business picks where on the risk curve to trade missed fraud against blocked customers.

  4. Why does the label feedback loop matter, and what makes it hard?

    Fraud is an adversarial, non-stationary problem: the people you're trying to stop actively change their tactics in response to your defenses, so a model trained on last month's fraud silently decays as new patterns emerge — the data distribution itself shifts against you (concept drift). The only way to stay effective is a continuous loop: collect ground-truth labels about which transactions were actually fraudulent, retrain the model on fresh data, deploy, and repeat. What makes this genuinely hard is label delay: the highest-quality label, a chargeback, can arrive weeks after the transaction, so if you wait for perfect labels your model is always reacting to old fraud. Systems mitigate this by supplementing slow chargebacks with faster signals — human analyst verdicts from the review queue, customer reports, and step-up outcomes — and by monitoring live proxy metrics (approval rate, score distribution, downstream fraud rate) to detect drift before the definitive labels confirm it. The feedback loop is effectively the system's moat: whoever labels and retrains fastest stays ahead of the adversary.

  5. How do class imbalance and false positives change how you build and measure this?

    Fraud is a tiny fraction of traffic — often well under 1% — which breaks naive approaches in two ways. Measurement: a model that blindly approves everything is over 99% accurate yet catches zero fraud, so raw accuracy is meaningless; you must evaluate with precision and recall and precision-recall AUC, which focus on how well you catch the rare positive class without drowning in false alarms. Training: with such imbalance you use techniques like resampling, class weighting, or cost-sensitive objectives so the model actually learns the minority pattern rather than ignoring it. Just as important is that the two error types have very asymmetric, real costs: a false negative is a chargeback (direct loss), but a false positive blocks a legitimate customer, costing the sale, eroding trust, and possibly losing that customer forever — so you explicitly measure the revenue and friction cost of blocking good users, tune thresholds against that tradeoff rather than a generic metric, and prefer step-up (2FA/review) over hard denial near the decision boundary to convert would-be false positives into verified approvals. Because the domain is adversarial and regulated, you also build in explainability (reason codes per decision) so declines can be justified to customers and regulators.

Check yourself — the answers, and why

Nine steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. A rules-only fraud system fails mainly because…

    Rules stay for hard blocks/compliance; the evolving subtle majority needs a learning model.

  2. The strongest fraud signals come from…

    Behavioral/velocity features from a fast feature store carry most of the signal.

  3. Velocity features must be updated by…

    Only real-time windowed counts catch a burst attack while it is happening.

  4. The system keeps working as fraud evolves via…

    Labels → retrain → deploy, plus drift monitoring to react before slow labels confirm.

  5. Because fraud is <1% of traffic, you must…

    Accuracy is meaningless under imbalance; measure the rare class and the cost of blocking good users.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Decide: return allow / deny / step-up on every transaction, inline, in ~80ms.
  • Score risk: turn dozens of signals into one fraud probability.
  • Compute features: velocity, device, geo and history served from a feature store.
  • Learn: feed chargebacks and analyst verdicts back to retrain as fraud evolves.
  • Explain: a reason code per decision for disputes and compliance.

The qualities that shape everything

Each one names the mechanism that buys it.

A verdict without delaying checkout
A decision service inline in the payment path returns allow/deny/step-up inside a ~50–100ms budget — precompute features, keep the model small, degrade rather than time out a payment.
Rich signal inside the budget
Precompute behavioral features — velocity, device, geo, account age, chargebacks — and serve them from a low-latency online feature store instead of scanning history per request.
Catch patterns no human wrote down
An ML model (gradient-boosted trees) scores a fraud probability over the features; rules veto the obvious and a tuned threshold maps the score to a decision.
Catch a burst attack while it happens
Stream every event through windowed aggregations (Kafka + Flink) that write velocity counts straight into the online store, so the next decision sees the spike seconds later.
Stay ahead of an adapting adversary
A label feedback loop — chargebacks, analyst verdicts, reports — retrains the model regularly, with drift monitoring to react before the slow labels confirm decay.
Nuance, rings and "why declined?"
Three-way decisioning steps up the uncertain middle, graph features link accounts by shared device/card/IP to catch fraud rings, and reason codes explain each verdict.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Rules + a learning model over a rules-only engine

Fixed thresholds are static against an adaptive adversary — fraudsters probe until they slip just under, and blunt limits false-positive on real outliers. Keep rules for hard blocks and compliance; let a model learn the subtle, shifting majority.

Precomputed features in a store over recomputing history per request

Scanning a card’s full history on every transaction blows the ~80ms budget. Features are precomputed and maintained incrementally, so the scorer reads velocity and history in milliseconds instead of recomputing them.

Streaming aggregations over a nightly batch job

Velocity is only useful fresh to seconds — a nightly batch can’t see the 50 rapid transactions a fraudster is firing right now. Streaming windows keep the decisive features current; batch still computes the slow, long-term ones.

Step-up the uncertain middle over a hard allow/deny cutoff

A binary cutoff treats a coin-flip 0.52 like a 0.99 — you either eat the fraud or block a real customer on a case the model itself is unsure about. A 2FA challenge lets the genuine customer through and yields a high-quality label.

Precision/recall + PR-AUC over raw accuracy

Fraud is under 1% of traffic, so a model that approves everything scores 99% accuracy and catches nothing. You measure the rare class and weigh the asymmetric cost of blocking a good customer against eating a chargeback.

The answer, out loud

What a strong answer to “Design a Fraud Detection System” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–4 min

    What we’re actually optimizing

    Let me start by naming the objective, because fraud is one of the few systems where the two errors cost completely different things. If I miss fraud, the business eats a chargeback. If I block a real customer, I lose the sale and maybe the customer, and nobody files a ticket to tell me I did it. So I’m not chasing accuracy, I’m picking a point on a trade-off curve. Concretely, every transaction gets one of three answers — allow, deny, or a step-up challenge — and I have to give it inline, while the checkout is waiting, in something like eighty milliseconds.

  2. 4–9 min

    Why a rules engine alone won’t hold

    The obvious first design is a rules engine, and I want to be fair to it before I move past it. Rules are fast, explainable and editable, and for blocklists, sanctions and impossible velocity they’re exactly right. What they can’t survive is an adversary. A fixed threshold is a thing to be discovered, and once it’s discovered the attacker sits just underneath it, while that same blunt limit declines a real customer making an unusually large purchase abroad. Fraud is a shifting combination of signals, so the honest shape is hybrid — rules for the clear-cut and the legally required, a model for the subtle majority.

    Built in step 1: Static rules break
  3. 9–15 min

    A decision service in the payment path

    So the centre of the design is a decision service sitting inline in checkout. It takes the transaction, runs the fast rules first, gathers features, scores, and maps that score to one of the three verdicts. The constraint that shapes everything after it is the budget — roughly fifty to a hundred milliseconds for all of it. That’s why I can’t go and read a card’s history at decision time, why the model has to be small enough to score in a few milliseconds, and why I’d rather return a degraded verdict than hold up a payment.

    Built in step 2: Score, then decide, fast
  4. 15–22 min

    The signal is context, not the transaction

    The interesting work here is the features, not the model. A single transaction — forty dollars, this card, this IP — tells me almost nothing. The signal is contextual: how many transactions this card, device and IP have made in the last minute, hour and day; whether the device is new; how far this is from the last location; how old the account is; whether there are chargebacks behind it. None of that can be computed inside the request, so it’s precomputed and served out of an online feature store at millisecond latency. In fraud I’d say feature work beats model choice most of the time.

    Built in step 3: Real-time features
  5. 22–28 min

    Scoring, and who owns the threshold

    On top of those I’d run gradient-boosted trees. Tabular data, dozens of features, has to score fast and be retrained often — that’s the sweet spot, and the model family isn’t the interesting decision. What is interesting is that the model outputs a probability, and a probability isn’t a decision. Someone has to choose where to auto-approve, where to hard-decline, and how wide the band between them is, and that’s a business choice: it is literally how much fraud loss you’ll accept to avoid how much customer friction. I’d keep hard rules able to veto the score outright.

    Built in step 4: The ML model
  6. 28–34 min

    Keeping velocity fresh

    Velocity only works if it’s fresh. If those counts come out of a nightly batch, the attack that matters — a card being hammered right now — is invisible until tomorrow, by which time it’s over. So every event goes onto a stream, and streaming jobs maintain windowed counts per card, device and IP and write them straight into the online store. The gap between something happening and the next decision seeing it is seconds. Slow, long-horizon features can still come from batch; the decisive ones have to be streamed.

    Built in step 5: Streaming aggregations
  7. 34–39 min

    Learning from outcomes

    None of this holds still, so I need a feedback loop. Ground truth comes from chargebacks, from analyst verdicts on reviewed cases, and from customer reports, and it feeds a regular retrain. The awkward part is the delay — a chargeback can take weeks, so by the time the truth lands the pattern has moved on. I’d compensate with the faster signals I have, analyst decisions and challenge outcomes, and by watching live proxies: approval rate, decline rate by segment, the shape of the score distribution. Those move before the labels do, and they tell me the model is drifting while I can still act.

    Built in step 6: The label feedback loop
  8. 39–43 min

    The middle band, and the ring

    Two refinements before I’d call it done. The first is that middle band. If the model says roughly a coin flip, forcing allow or deny throws away what it just told me — challenge instead, with 2FA or human review, and a genuine customer gets through while a fraudster usually doesn’t. The second is that fraud is rarely one account. I’d add graph features linking accounts by shared device, card, IP or address, so a confirmed-bad account reaches the rest of its ring instead of each one being relearned transaction by transaction.

    Built in step 7: Decisioning, step-up & graphs
  9. 43–45 min

    Where I’d go next

    So the trade-off in one line: every knob moves fraud loss against customer friction, and there’s no setting that improves both. I’d rather sit slightly looser and challenge more than decline more, because the declined good customer is the error I never hear about. With more time, the two things I’d go to are the imbalance — fraud is under one percent of traffic, so I’d be measuring precision, recall and PR-AUC rather than accuracy — and the reason code I owe on every decline, which has to be designed in rather than reconstructed afterwards.

What this teaches

Learn system design by building a real-time fraud detection system like Stripe Radar step by step. An interactive guide covering why static rules fail, real-time feature computation (velocity, device, geo) from a feature store, ML risk scoring blended with rules, streaming aggregations under a tight latency budget, the label feedback loop that retrains models, allow/deny/step-up decisioning with graph features, and the unhappy paths (concept drift, false positives, class imbalance, explainability).

Key takeaways

  • Static rules are gameable and blunt — fraud is adaptive and subtle, so it needs learning.
  • A decision service returns allow/deny/step-up inline, under a tight (~80ms) latency budget.
  • Real-time contextual features (velocity, device, geo, history) from a feature store carry the signal.
  • An ML model scores the subtle patterns; rules veto the obvious; a tuned threshold decides.
  • Streaming aggregations keep velocity features fresh enough to catch attacks in flight.
  • A label feedback loop (chargebacks, reviews) retrains the model as fraud evolves.
  • Three-way decisioning + graph features + reason codes handle nuance, rings, and compliance.

Concepts covered

  • What is fraud detection?
  • Static rules break
  • Score, then decide, fast
  • Real-time features
  • The ML model
  • Streaming aggregations
  • The label feedback loop
  • Decisioning, step-up & graphs
  • Drift, false positives & imbalance
built to catch the thief in 80 milliseconds without blocking the customer — make the calls, kill the model, run the gauntlet.
Finished this one? 0 / 65 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs