Requirements
- Users share a unique referral code or link to invite new users
- Referrer earns a reward when a referred user completes a qualifying action (signup, first purchase)
- Support one-sided and two-sided rewards (both referrer and referee get rewards)
- Prevent fraud: referral fraud, self-referrals, fake accounts
- 100M users, 1M referrals/month
Data Model
ReferralCode(code VARCHAR UNIQUE, referrer_id UUID, created_at,
uses_count INT, max_uses INT NULL)
Referral(referral_id UUID, referral_code VARCHAR, referrer_id UUID,
referee_id UUID NULL, -- NULL until referee signs up
status ENUM(PENDING,QUALIFIED,REWARDED,FRAUD,EXPIRED),
click_at TIMESTAMP, signup_at TIMESTAMP, qualified_at TIMESTAMP,
ip_address, device_fingerprint, utm_source)
ReferralReward(reward_id UUID, referral_id UUID, user_id UUID,
reward_type ENUM(CREDIT,DISCOUNT,CASH,POINTS),
amount DECIMAL, status ENUM(PENDING,ISSUED,EXPIRED),
issued_at, expires_at)
Referral Link Flow
- Referrer requests their referral code: GET /referral/code → generate or return existing code
- Referrer shares URL: https://app.example.com/signup?ref=ABC123
- Prospect clicks link → server stores referral_code in cookie/session (TTL=30 days)
- Prospect signs up → read referral_code from cookie → create Referral record (status=PENDING)
- Qualifying action completed (first purchase) → update status=QUALIFIED → issue rewards
Reward Issuance
Rewards should be issued asynchronously after the qualifying event:
def on_qualifying_action(user_id, order_id):
referral = db.query("SELECT * FROM Referral WHERE referee_id = :uid
AND status = 'PENDING'", uid=user_id).first()
if not referral: return
# Issue rewards in a transaction
BEGIN TRANSACTION
UPDATE Referral SET status='QUALIFIED', qualified_at=NOW() WHERE referral_id=:id
INSERT INTO ReferralReward (referral_id, user_id=referral.referrer_id, amount=10, status='PENDING')
INSERT INTO ReferralReward (referral_id, user_id=referral.referee_id, amount=5, status='PENDING')
COMMIT
# Apply rewards to accounts (async via job queue)
enqueue_reward_job(referral_id)
Fraud Prevention
Referral fraud is common and costly. Key fraud signals:
- Self-referral: same user creates a new account and refers themselves. Detection: same email domain, same IP address, same device fingerprint, same payment method as the referrer. Block if any match.
- Fake accounts: mass creation of fake accounts to farm referral rewards. Detection: velocity limit (max N signups from same IP per day), email domain blacklist (disposable email providers), phone number verification required for reward.
- Reward farming rings: coordinated groups of real users cross-referring. Detection: graph analysis — a cluster of users all referring each other is suspicious.
- Referral abuse: referring using shared/public links (posted on deal sites). Detection: velocity limit on a single referral code (max uses/day).
Fraud holds: instead of issuing rewards immediately, hold for 7-30 days (cooling period) to allow fraud detection. If account is flagged as fraud within the holding period, cancel the reward.
Attribution
Attribution determines which referral source gets credit when a user has multiple touch points. Last-touch attribution (default): the referral code from the most recent click gets credit. First-touch attribution: the first referral code the user clicked gets credit. The referral_code stored in the cookie is typically last-touch — overwrite with new code on each click. Store click history for analysis and dispute resolution.
Referral Code Generation
Codes must be short (for sharing), unique, and hard to guess. Options: (1) Random alphanumeric: 8 characters from [A-Z0-9] = 36^8 ≈ 2.8 trillion combinations. Collision probability with 100M users: < 0.001%. (2) User-ID-based encode: base62-encode the user_id. Shorter, deterministic, but exposes user ID count. (3) Vanity codes: let users set custom codes ("JOHN10"). Store in ReferralCode with UNIQUE constraint. Generate code on first request; cache in Redis key=referral_code:{user_id} permanently.
Key Design Decisions
- Cookie attribution with 30-day TTL — captures organic referrals without requiring immediate signup
- Async reward issuance with fraud hold — prevents paying out before fraud is detected
- Device fingerprint + IP check for self-referral detection — primary fraud signal
- Velocity limits on code use and signups per IP — blocks mass fake account creation
- Two-sided rewards — incentivizes both sharing and completing the qualifying action
Shopify system design covers referral and growth programs. See common questions for Shopify interview: referral and growth system design.
Uber system design covers referral programs and driver/rider incentives. Review patterns for Uber interview: referral and incentive system design.
Airbnb system design covers referral growth systems. See design patterns for Airbnb interview: referral and growth system design.