Waitlist System Low-Level Design

What is a Waitlist System?

A waitlist system manages controlled access rollout for a product launch or high-demand feature. Users sign up, get a position number, and are invited off the waitlist in batches. Famous examples: Robinhood’s 2015 launch (1 million signups in days), Clubhouse’s invite-only growth, Gmail’s invitation system in 2004. A good waitlist system creates urgency (show your position), incentivizes referrals (jump the line), and gives the product team controlled rollout.

Requirements

  • Users sign up with email; receive confirmation with their waitlist position
  • Referral system: each user gets a referral link; each referral moves you up N spots
  • Admin dashboard: see total signups, invite N users off the waitlist
  • Invite flow: send invitation email with a time-limited activation link
  • Priority queue: some users (press, influencers) get expedited access
  • Handle 100K signups in the first hour of a product launch

Data Model

WaitlistEntry(entry_id UUID, email VARCHAR UNIQUE,
              position INT,                  -- assigned at signup; lower = sooner
              referral_code VARCHAR UNIQUE,  -- e.g., 8-char alphanumeric
              referred_by_code VARCHAR,      -- referrer's code (nullable)
              referral_count INT DEFAULT 0,  -- how many others used your code
              priority_boost INT DEFAULT 0, -- admin-assigned boost
              effective_position INT,        -- position - (referral_count * spots_per_referral) - priority_boost
              status ENUM(WAITING, INVITED, ACTIVATED, DISQUALIFIED),
              invited_at TIMESTAMP,
              created_at TIMESTAMP)

InviteToken(token_id UUID, entry_id UUID, token VARCHAR UNIQUE,
            expires_at TIMESTAMP, used_at TIMESTAMP)

Signup Flow

def signup(email, referral_code=None):
    if db.exists('WaitlistEntry', email=email):
        raise DuplicateError('Already on waitlist')

    position = db.count('WaitlistEntry') + 1  # next sequential position
    code = generate_referral_code()            # 8-char random alphanumeric

    entry = db.create(WaitlistEntry(
        email=email, position=position,
        referral_code=code,
        referred_by_code=referral_code,
        effective_position=position  # recalculated when referrals come in
    ))

    if referral_code:
        referrer = db.get(WaitlistEntry, referral_code=referral_code)
        if referrer:
            db.increment(referrer.id, 'referral_count', 1)
            recalculate_effective_position(referrer.id)

    send_confirmation_email(email, position=position, referral_link=f'/join?ref={code}')
    return entry

Position Calculation

SPOTS_PER_REFERRAL = 5

def recalculate_effective_position(entry_id):
    entry = db.get(entry_id)
    effective = (entry.position
                 - (entry.referral_count * SPOTS_PER_REFERRAL)
                 - entry.priority_boost)
    db.update(entry_id, effective_position=max(1, effective))

Show users their effective position, not raw position: “You’re #1,247. Refer 3 friends to move to #1,232.” Recalculate on every new referral. Use a background job for bulk recalculations after large events.

Invite Batch

def invite_batch(n):
    # Select top N by effective_position who are still WAITING
    entries = db.query('''
        SELECT * FROM WaitlistEntry
        WHERE status = 'WAITING'
        ORDER BY effective_position ASC
        LIMIT ?
    ''', n)

    for entry in entries:
        token = generate_secure_token()
        db.create(InviteToken(entry_id=entry.id, token=token,
                              expires_at=now() + timedelta(days=7)))
        db.update(entry.id, status='INVITED', invited_at=now())
        send_invite_email(entry.email,
                          activation_url=f'https://app.example.com/activate?token={token}')

High-Volume Signup Handling

100K signups in the first hour = ~28 signups/second sustained. Use a queue to decouple HTTP response from DB write:

  1. HTTP endpoint validates email format, writes to Redis list (RPUSH signup_queue email)
  2. Returns 200 immediately: “You’re on the list! Check your email.”
  3. Worker processes queue: dequeues, assigns position, inserts to DB, sends email
  4. Position is approximate during queue processing (OK for UX)

Key Design Decisions

  • Effective position = raw position – referral bonus — incentivizes sharing without giving exact position guarantee
  • Referral code at signup — every user gets one; reduces friction for sharing
  • Time-limited invite tokens — tokens expire in 7 days; unclaimed invites go back to next batch
  • Queue-based signup for launch surges — decouple HTTP response from DB write to handle spikes
  • Priority boost as separate field — ops can fast-track press/influencers without modifying raw position

{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How do you design a waitlist system that handles 100K signups in an hour?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Decouple the HTTP response from DB writes using a queue. The signup endpoint validates the email, pushes it to a Redis list (RPUSH signup_queue email), and immediately returns 200 ("You’re on the list!"). A pool of workers dequeues and inserts to the DB asynchronously. This prevents the DB from becoming the bottleneck during a spike. Position assignment is slightly delayed but that’s acceptable — users get a confirmation email within seconds.”}},{“@type”:”Question”,”name”:”How does referral-based queue jumping work?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Each user gets a unique referral code at signup. Effective position = raw_position – (referral_count × spots_per_referral) – priority_boost. When someone signs up using your referral code, your referral_count increments and your effective_position is recalculated. The invite batch queries by effective_position ASC, so users with more referrals get invited sooner. Show users their effective position and how many referrals they need to move up.”}},{“@type”:”Question”,”name”:”How do you generate secure invite tokens?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Use secrets.token_urlsafe(32) (Python) or crypto.randomBytes(32).toString("base64url") (Node.js) to generate a 256-bit cryptographically random token. Store the token (hashed with SHA-256) in the InviteToken table alongside the entry_id and expiry. Set expiry to 7 days. When the user clicks the activation link, hash the provided token, look it up in the DB, verify it’s not expired and not used, then mark used_at=NOW() and activate the account.”}},{“@type”:”Question”,”name”:”How do you handle expired invitations that were never claimed?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”A daily cron job queries InviteToken WHERE expires_at < NOW() AND used_at IS NULL. For each expired token, set the WaitlistEntry status back to WAITING. These entries rejoin the queue at their effective_position. The next invite batch will include them. Optionally send a "your invitation expired — you’re back on the list" email. This prevents wasted invite slots from users who missed the email.”}},{“@type”:”Question”,”name”:”How do you give priority access to press and influencers?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Add a priority_boost INT field on WaitlistEntry (default 0). Admin sets this to a large value (e.g., 100000) for priority users. Effective_position = raw_position – referral_bonus – priority_boost, so priority users sort to the top regardless of their raw position. This is cleaner than directly setting effective_position because the original position is preserved for auditing. Use a separate ENUM status EXPEDITED if you want to track how many were fast-tracked.”}}]}

Waitlist and controlled rollout systems are discussed in Shopify system design interview questions.

Referral and waitlist system design is covered in Airbnb system design interview preparation.

Waitlist and invite system design patterns appear in Stripe system design interview guide.

Scroll to Top