What Is a Referral Service?
A Referral Service incentivizes existing users to invite new users by rewarding both parties when a successful conversion occurs. It must accurately track who referred whom, prevent self-referrals and fraud, detect when a referral has converted (signed up, made a purchase, etc.), and trigger reward payouts reliably without double-counting.
Data Model
TABLE referral_codes (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL UNIQUE, -- one code per user
code VARCHAR(32) UNIQUE NOT NULL,
created_at DATETIME DEFAULT NOW()
);
TABLE referrals (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
referrer_id BIGINT NOT NULL, -- user who shared the code
referee_id BIGINT NOT NULL UNIQUE, -- new user; one referrer only
code VARCHAR(32) NOT NULL,
status ENUM('pending', 'converted', 'rewarded', 'voided') DEFAULT 'pending',
conversion_event VARCHAR(64), -- e.g. 'first_purchase'
converted_at DATETIME,
rewarded_at DATETIME,
created_at DATETIME DEFAULT NOW(),
INDEX idx_referrer (referrer_id),
INDEX idx_code (code)
);
TABLE referral_rewards (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
referral_id BIGINT NOT NULL REFERENCES referrals(id),
user_id BIGINT NOT NULL,
reward_type ENUM('credit', 'coupon', 'points') NOT NULL,
amount DECIMAL(10,2),
issued_at DATETIME DEFAULT NOW()
);
Core Workflow
- Code generation: On user signup, generate a unique alphanumeric code (e.g., 8 characters, base-36) and insert into
referral_codes. - Attribution: When a new visitor lands via a referral link, store the code in a cookie or session. On signup, look up the code, validate the referrer exists and is not the same account, then insert a
referralsrow withstatus = pending. - Conversion detection: The service subscribes to domain events (e.g.,
order.completed) via a message queue. On receiving a qualifying event for a referee, it updatesreferrals.statustoconvertedand recordsconverted_at. - Reward issuance: A worker transitions status to
rewardedand inserts rows intoreferral_rewardsfor both referrer and referee, then calls the appropriate reward subsystem (credit, coupon, or points service).
Failure Handling
- Duplicate conversion events: The message consumer must be idempotent. Use the
referee_id UNIQUEconstraint and a conditional update:UPDATE referrals SET status='converted' WHERE referee_id=? AND status='pending'. Zero rows affected means already processed. - Reward delivery failure: Issue rewards inside a database transaction alongside the status update. If the downstream call fails, roll back and rely on the queue to redeliver. Use an outbox pattern to guarantee at-least-once reward delivery.
- Fraud / self-referral: At attribution time, reject if
referrer_id = new_user_id, if the referrer account is flagged, or if the device fingerprint matches the referrer. - Code expiry: Support optional
expires_atonreferral_codesfor campaign-limited programs.
Scalability Considerations
- Event-driven architecture: Decoupling conversion detection from the order flow via a message queue (Kafka, SQS) prevents the referral logic from adding latency to checkout.
- Code lookup caching: Cache the
referral_codestable in Redis. A read-through cache with a long TTL works well since codes rarely change. - Analytics separation: Aggregate referral metrics (conversion rates, top referrers) in a read replica or data warehouse rather than querying the operational database.
- Horizontal scaling: The reward worker pool can scale independently based on queue depth.
Summary
A Referral Service is fundamentally an event-driven state machine: codes are minted, referrals are attributed, conversion events trigger status transitions, and rewards are issued exactly once. The UNIQUE constraint on referee_id is the single most important correctness guarantee in the schema.
See also: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering
See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering
See also: Shopify Interview Guide