System design sample solution

System design: detect fraudulent credit cards

A step-by-step system design answer you could actually give in 60 minutes.

System design interview · Sample 60-minute answer · Not Booking.com's actual design · Independent, not affiliated with Booking.com

The system design question

Guests can book now and pay later, on or before check-in. A third-party API gives us newly reported fraudulent cards. Design a service that finds and blocks bookings made with those cards.

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Pulling new reports from the fraud APIBuilding our own fraud model
Finding existing bookings made with a reported cardHow card numbers are stored and secured
Checking new bookings against cards we already know are badChargebacks and disputes
Blocking a booking and telling the guest and hotelThe payment flow itself

Then agree on what "good" means:

  • Never charge a reported card: once a card's reported, no booking on it gets charged, even one made months ago.
  • Don't miss a report: if the fraud API or our service goes down for a while, we catch up on everything we missed.
  • Fast enough: a new report blocks its bookings within a few minutes, well before any scheduled charge.
  • Fair to real guests: a wrong report shouldn't silently cancel someone's holiday. They get a chance to fix it.

Rough numbers (5 minutes)

These are assumptions, made up but reasonable: 1.5 million bookings a day with 40% of them pay later, about 60 days on average between booking and paying, and 100,000 newly reported cards a day from the fraud API.

~600K
new pay later bookings a day
~36M
unpaid pay later bookings at any time
~1
new bad card per second, on average
~2 GB
for a card to bookings lookup
QuestionRough mathWhat it means
How many open bookings to check?600K a day × 60 daysAbout 36 million unpaid bookings, any of which could match a new report
How big is the lookup?36 million × about 50 bytes for a card fingerprint and booking IDAbout 2 GB, small enough for one indexed table or key-value store
How much work per report?100K reports a day ÷ 86,400 secondsAbout 1 a second, each one a single lookup by card. Tiny

The overall design (10 minutes)

Fraud APInew bad cardsFeed readerpolls, saves placeBad card queueone message per cardMatchercard to bookingsBooking and paymentshold and notify
New reports come in from the fraud API, get saved and queued, and the matcher finds every open booking on that card and puts it on hold.
  1. When a guest books with pay later, the payment provider gives us a card fingerprint, a stable ID for the card that isn't the card number. We save it with the booking and add it to a card to bookings lookup.
  2. A feed reader polls the fraud API every minute, turns each reported card into the same fingerprint, saves it in a bad cards table, and puts a message on a queue.
  3. The matcher reads the queue, looks up every unpaid booking on that card, and asks the booking service to put each one on a payment hold.
  4. A held booking won't be charged. The guest gets asked for a different card, and the hotel sees the booking's at risk.
  5. New bookings check the bad cards table when they're made, and the payment service checks it once more right before any charge.

What the apps need to do

ActionWho does itWhat's sent
Check a cardBooking and payment servicesCard fingerprint. Returns whether it's reported and when
Hold a bookingMatcherBooking ID, reason, report ID
Replace the cardGuestBooking ID, new card from the payment page
Review a holdFraud teamBooking ID, release or cancel

What we store and where

DataWhere it goesWhy
Bad cards (fingerprint, when reported, source)Relational database with a unique rule on fingerprintSmall, and a repeated report just hits the unique rule
Card fingerprint to open bookingsIndexed table or key-value store, split by fingerprintTurns 'who used this card?' into one lookup
Feed positionSame database as bad cardsSo we can pick up where we left off after a crash
Holds and decisionsBooking database, plus an audit logWho held what, why, and what happened next

Step 1: Not missing a report (10 minutes)

Third-party APIs go down, rate limit and change. Assume the fraud API gives us reports since a cursor or timestamp. The reader asks for everything after its saved position, saves the new bad cards, and only then moves its position forward. If it crashes in between, it fetches the same page again next time.

Fetching the same report twice is fine. The bad cards table has a unique rule on the fingerprint, so a repeat changes nothing, and the matcher's actions are safe to repeat too. That's idempotency, and it's what lets us retry freely.

Put each new card on a queue instead of matching inside the reader. The reader stays simple and fast, and if matching is slow or broken, messages wait instead of getting lost.

Watch how far behind the feed is. If we haven't fetched anything new in 15 minutes, page someone. If the API offers a full list, compare against it once a day to catch anything that slipped through.

Step 2: Matching new reports to old bookings (12 minutes)

This is the twist in the question. A card gets reported weeks after someone booked with it. Scanning 36 million open bookings for every report would be silly, so we keep a lookup from card fingerprint to the unpaid bookings that use it. Add to it when a pay later booking is made, remove from it when the booking's paid or cancelled. Those updates can come from booking events.

We never keep raw card numbers in this service. The payment provider already gives us a fingerprint per card, and we turn the fraud API's card numbers into the same fingerprint as soon as they arrive, then throw the number away.

There's a race to think about. A booking could be made on a card at the same moment that card's being reported. Handle it from both sides: the reader saves the bad card first and then matches, and a new booking adds itself to the lookup first and then checks the bad cards table. Whichever happens second sees the other one, so the booking gets caught either way.

One last check before charging

Even if the matcher's down for an hour, the payment service checks the bad cards table right before it charges anything. It's one lookup, and it means a missed match can't turn into a charge on a reported card.

Step 3: What blocking actually means (8 minutes)

Ask what 'block' should mean, because it changes the design. Cancelling right away is simple, but fraud reports are sometimes wrong, and cancelling a real guest's stay is a big deal. A gentler default works better: put the booking on a payment hold, stop any scheduled charge, and ask the guest to add a different card.

If they add a new card within, say, 48 hours, the hold comes off. If they don't, or check-in is very close, cancel the booking and give the room back to the hotel. Big or unusual bookings can go to the fraud team to look at first.

Every hold and cancellation goes in an audit log with the report that caused it. Keep each action safe to repeat by keying it on booking and report, so a retried message doesn't send the guest three emails.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
Polling the fraud APIWaiting for it to push to usWe control the pace and can always catch up from our saved positionThe provider offered reliable webhooks and we needed seconds, not minutes
A lookup from card to bookingsScanning bookings for each reportOne lookup instead of 36 million rows, per reportNever, at this size
Holding the booking firstCancelling right awayWrong reports happen, and a hold gives real guests a way outThe report source was always right, or fraud losses were climbing
A final check before chargingTrusting the matcher aloneOne cheap lookup covers any bug or delay in matchingHard to see a reason to drop it

What could go wrong (5 minutes)

What happensWhat we do about it
The fraud API is down for hoursWe keep our saved position and catch up when it's back. An alert fires if we're too far behind
The matcher falls behindMessages wait on the queue, and the check before charging still stops any bad charge
The same card gets reported twiceThe unique rule on fingerprint ignores the second one
A report turns out to be wrongThe guest's booking is on hold, not cancelled. The fraud team releases it, and the card's marked as cleared
A booking and a report land at the same momentBoth sides write first, then check, so one of them catches it

Follow-up questions (5 minutes)

How do you know it's working?

Track how long it takes from a report arriving to its bookings being held, how many charges the last check stops (should be near zero), and how many holds get released as wrong.

What if we add a second fraud provider?

Add another reader that writes to the same bad cards table and queue. Keep the source on each report so you can see which provider is right more often.

What about guests who already paid?

Out of scope for blocking, since the money's taken. Send those bookings to the fraud team, who can look at refunds or chargebacks.

How the 60 minutes add up

MinutesWhat you're doing
0–5Agree on what you're building
5–10Rough numbers
10–20Draw the overall design
20–30Not missing a report
30–42Matching new reports to old bookings
42–50What blocking actually means
50–55What could go wrong
55–60Follow-up questions

Key concepts used here

Further reading

We wrote this sample answer ourselves.

Practice Loop is independent and isn't affiliated with or endorsed by Booking.com.