Design a referral system: data models, APIs, attribution, self-referral prevention

Tests data modeling with fraud guardrails and idempotent rewards. Cover: Users with nullable referred_by, ReferralEvents state table, async ledger attribution, and device-fingerprint self-referral blocks. Red flag: bare integer credit with no audit trail.
What's really being asked
This question tests your ability to model a one-to-many referral relationship with transactional integrity and fraud guardrails. Interviewers care about idempotency, auditability, and the boundary between signup and reward. They want to see that you understand race conditions on concurrent redemptions, double-spending of referral credits, and the difference between application-level checks and database-level constraints.
The full answer
First, schema design. A Users table should have a unique indexed referral_code and a nullable referred_by foreign key. A separate ReferralEvents table captures referral_code_used, invitee_user_id, device_fingerprint, ip_address, and a status enum such as pending, completed, or failed. This decouples account creation from reward payout. Second, API design. Expose a POST endpoint to generate or retrieve a unique code, a POST signup endpoint that accepts an optional referral_code and writes the pending event, and a POST complete-action or webhook endpoint that triggers reward fulfillment. Third, reward attribution. Use an async job or outbox pattern to move events from pending to completed. Write reward distribution as ledger entries with idempotency keys derived from the ReferralEvents row UUID so duplicate events do not double-credit. Fourth, self-referral prevention. Check the new user's email domain, phone hash, device fingerprint, or IP against the referrer's record. Enforce this in the application layer and, where possible, with a database check constraint or a unique partial index that blocks matching fingerprints.
The mistakes people make
Storing referral credit as a simple integer column on the Users table without an audit ledger. This loses traceability and creates race conditions when two invitees sign up simultaneously. Checking self-referral only by comparing raw email strings and ignoring device or IP fingerprinting. Making reward distribution synchronous inside the signup transaction, which adds latency and can roll back the account creation if the reward service fails. Using a boolean reward_granted flag instead of a state machine, which makes retries and observability difficult.
What usually comes next
How would you prevent brute-force enumeration of referral codes? How do you cap total rewards per referrer or expire codes after a time window? What happens if a user deletes their account after rewards were already paid out? How would you extend this to multi-level or tiered referrals?
A concrete example
Imagine a payment app where the referrer earns 201 and the invitee earns 51 after the invitee completes a first transaction. The signup API accepts a referral_code and creates a ReferralEvents row with status pending and stores a device fingerprint. A background job listens for the first-transaction event, verifies the invitee fingerprint does not match the referrer fingerprint, then inserts two ledger rows with idempotency keys based on the referral event UUID. It updates the event status to completed and sends notifications to both users. If the job retries, the ledger insert is idempotent and the reward is not duplicated.
Interview question
Which architecture best satisfies both auditability and concurrency safety for referral rewards?
- a.Compare raw email strings to block self-referral and synchronously add credit to the referrer's account balance
- b.Track reward status with a boolean reward_granted column on the Users table and retry failed signups
- c.Store referral credit as a simple integer column on the Users table and increment it inside the signup transaction
- d.Record each reward as an immutable ledger row keyed by the ReferralEvents UUID and fulfill payouts asynchronouslyCorrect
Why? this is the answer
Immutable ledger rows with idempotency keys derived from the ReferralEvents UUID guarantee exactly-once processing and a complete audit trail, while async fulfillment avoids blocking signup. A simple integer column creates race conditions and lacks traceability, and a boolean flag makes retries and observability difficult.
Just read this? Test yourself on what you have been reading.
Read the original → dev.to
- #referral system
- #api design
- #data modeling
- #fraud prevention
- #idempotency
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Every open role lists the topics its interview covers, so you can prepare for the real thing rather than guessing.
See open roles