What Is a Waitlist Service?
A waitlist service manages an ordered queue of users waiting for access to a product, feature, or limited resource. It tracks position, supports priority manipulation (referrals, tiers), admits users in batches, sends notifications on admission, and defends against gaming.
Functional Requirements
- Users can join a waitlist and receive a confirmation with their position
- Admins can admit N users at once (batch admission)
- Referrals move a user up the list (priority boost)
- Users receive a notification (email / push) when admitted
- Users can check their current position at any time
- Admins can view list stats and manually admit or remove users
Non-Functional Requirements
- Position reads should be fast (< 100 ms)
- Joins and priority updates must be consistent — no two users at the same effective position
- Admission is idempotent — re-running admission does not re-admit already-admitted users
- Anti-gaming: referral loops and fake accounts should not yield unbounded priority gains
- Scale to millions of waitlist entries per waitlist
Core Entities
| Entity | Key Fields |
|---|---|
| Waitlist | id, name, description, status ('open'|'closed'|'paused') |
| Entry | id, waitlist_id, user_id, status ('waiting'|'admitted'|'removed'), priority_score, joined_at, admitted_at, referral_code, referred_by |
| Referral | id, referrer_entry_id, referee_entry_id, created_at, boost_applied |
| Notification | id, entry_id, type ('join_confirm'|'position_update'|'admitted'), sent_at, channel |
Priority Scoring and Ordering
Ordering is determined by a priority score (higher = closer to the front) with joined_at as a tiebreaker. Base score is 0 at join time. Each confirmed referral adds a fixed boost (e.g., +100 points, capped per user). Tier or invite-only users can receive an initial score offset. The effective sort key is (priority_score DESC, joined_at ASC).
Position is computed as:
SELECT COUNT(*) + 1
FROM entries
WHERE waitlist_id = :wid
AND status = 'waiting'
AND (priority_score > :my_score
OR (priority_score = :my_score AND joined_at < :my_joined_at))
For large lists this count query is expensive. Cache position in Redis (sorted set) with ZRANK: score = priority_score * 1e12 - epoch_ms so lexicographic rank matches logical position.
Batch Admission Flow
Admin triggers admit(waitlist_id, n) | v SELECT top-N entries WHERE status = 'waiting' ORDER BY priority_score DESC, joined_at ASC FOR UPDATE SKIP LOCKED -- prevents concurrent double-admit | v UPDATE entries SET status = 'admitted', admitted_at = NOW() | v Publish admission events to queue (Kafka / SQS) | v Notification worker: send email/push per user | v Remove admitted entries from Redis sorted set | v Recompute / invalidate cached positions for top-K remaining users
Referral-Based Priority Boosts
Each entry gets a unique referral code on join. When a new user joins with ref=CODE, a Referral row is created linking referee to referrer. On join confirmation (or email verification to reduce fraud), the referrer's priority_score is incremented and the Redis sorted set is updated atomically. Boost is capped (e.g., max +500 total from referrals) to prevent runaway gaming.
Anti-Gaming Measures
- Email verification required before a referral boost is credited
- Per-account referral cap: max N boosts regardless of referrals count
- Referral loop detection: A refers B refers A — detect cycles in the Referral graph and reject or nullify the loop leg
- Device / IP deduplication: flag entries from the same device fingerprint or IP subnet as suspicious; hold boosts for manual review
- Velocity limits: cap how many referrals can be credited per hour per referrer
Notification on Admission
Admission events are published to a queue. A notification worker consumes them, generates a personalized access link (time-limited signed token), and sends via email and/or push. Idempotency key = (entry_id, 'admitted') prevents duplicate sends if the worker retries. Notification status is persisted so the admin dashboard can show delivery confirmation.
Scaling Considerations
- Position reads at scale: Redis sorted set with O(log N) ZRANK; no DB query for position checks
- Write contention during admission:
SKIP LOCKEDin Postgres allows parallel admission batches without deadlocks - Priority score updates: atomic ZINCRBY in Redis + DB update in a transaction; Redis is source of truth for ordering, DB is source of truth for persistence
- Millions of entries: shard sorted set by waitlist_id; archive admitted/removed entries to cold storage
Interview Talking Points
- Why use a sorted set in Redis instead of computing position from DB every time?
- How do you keep Redis and Postgres consistent if the process crashes mid-update?
- How do you handle the referral cap and loop detection without a full graph traversal on every join?
- What happens if two admin threads try to admit the same batch simultaneously? (SKIP LOCKED)
- How would you support multiple waitlists with different admission rules (FIFO vs. priority vs. lottery)?
See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
See also: Shopify Interview Guide