Low Level Design: Waitlist Service

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

EntityKey Fields
Waitlistid, name, description, status ('open'|'closed'|'paused')
Entryid, waitlist_id, user_id, status ('waiting'|'admitted'|'removed'), priority_score, joined_at, admitted_at, referral_code, referred_by
Referralid, referrer_entry_id, referee_entry_id, created_at, boost_applied
Notificationid, 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 LOCKED in 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)?
{ “@context”: “https://schema.org”, ” @type”: “FAQPage”, “mainEntity”: [ { “@type”: “Question”, “name”: “What is a waitlist service and how are positions tracked efficiently?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “A waitlist service queues users who want access to a product or resource when demand exceeds available capacity and admits them in a controlled order. Positions are tracked using a sorted set in Redis where the score is the join timestamp, allowing O(log n) rank lookups by member. A monotonically increasing sequence number assigned at join time provides a stable ordering that is unaffected by deletions elsewhere in the list.” } }, { “@type”: “Question”, “name”: “How does batch admission work when capacity opens up on a waitlist?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “When new capacity becomes available a batch admission job determines how many spots opened and pops that many entries from the head of the sorted set. For each admitted user the service atomically transitions their status from waiting to admitted in the database, sends an invitation email or push notification, and reserves their slot with a time-limited acceptance window. If a user does not accept within the window their slot is released back and the next user in line is offered it.” } }, { “@type”: “Question”, “name”: “How do referral-based priority boosts work in a waitlist?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Each user on the waitlist receives a unique referral code. When someone signs up using that code, the referrer’s position score is improved by a fixed delta—for example moving them up by a set number of positions per successful referral. The boost is applied atomically to the sorted set score. To preserve fairness, a cap limits the maximum boost any single user can accumulate, and the system records which referrals have already been credited to prevent double-counting.” } }, { “@type”: “Question”, “name”: “How does a waitlist service prevent gaming and manipulation?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Anti-gaming controls include email verification at sign-up to prevent fake accounts, rate limiting referral credits per IP and device fingerprint to block bulk referral fraud, and deduplication of referred users by email and phone number. Anomaly detection flags accounts that accumulate referrals at statistically unusual rates for manual review. Referral rewards are held in a pending state until the referred user completes a verification step, so creating throwaway accounts to self-refer does not yield an immediate boost.” } } ] }

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

Scroll to Top