At 10 a.m., a popular concert goes on sale. Thousands of people refresh their browsers together, while the booking database and payment service can support only a fraction of them at once.
Adding application servers does not automatically increase those downstream limits. A virtual waiting room controls how quickly visitors enter the purchasing journey, keeping demand within a capacity the system can sustain.
In a system design interview, the challenge is to protect the booking service while making admission understandable, recoverable and difficult to bypass. The queue itself becomes a public promise, so its behaviour during failures matters as much as its normal ordering.
Introduction
A waiting room separates arriving at a sale from being allowed to shop. Instead of letting every visitor repeatedly call expensive inventory and checkout endpoints, it serves a cheap waiting experience and gradually issues admission sessions.
The platform still needs a reservation system that prevents two shoppers from owning the same seat. Admission does not provide inventory consistency, and a position in the queue does not guarantee that tickets remain available when the visitor enters.
For this design, visitors can join an event queue, retain their identity across refreshes, receive status updates and enter the shop when capacity is available. Admitted users have a bounded shopping session. The system can pause admission if the booking service becomes unhealthy without discarding everyone's place.
We will first define the fairness policy and workload, then design queue membership, admission tokens, capacity control and recovery. The examples describe a hypothetical ticketing platform rather than the internal implementation of any ticket seller.
Define What the Waiting Room Guarantees
The waiting room should limit both new arrivals into the shop and the number of active shopping sessions. A rate limit alone cannot control concurrency when users remain in the purchase flow for a long time.
Visitors should retain their place when refreshing or briefly disconnecting. Joining the queue twice from the same recognised session should return the existing entry where the product's identity policy allows it. A lost status response must not silently send the visitor to the back.
The booking application must validate admission on protected requests. Showing a waiting page is ineffective if a visitor can bypass it by calling a direct inventory or checkout URL.
The queue policy should be published in language visitors can understand. An exact first-in-first-out promise differs from a random draw among people who arrived before opening. Approximate global ordering is another choice and should not be presented as an exact sequence.
Finally, the system should distinguish being queued, being offered admission and holding an active shopping session. Each state has a different timeout and recovery rule. Compressing them into one boolean such as allowedToBuy leaves important races undefined.
Separate Admission from Inventory Ownership
An admitted shopper is allowed to use the shop. They do not own a seat until the reservation system has successfully applied its inventory rules.
Two admitted users can still click the same seat at almost the same time. The inventory transaction must decide which reservation succeeds using an appropriate constraint, conditional update or transaction. Reducing traffic makes that transaction easier to operate, but does not replace it.
Similarly, a payment arriving after a reservation expires is a payment-and-inventory workflow problem. A valid admission session cannot revive an expired seat hold after the seat has been allocated elsewhere.
Keep these identities separate: queue entry, admission grant, shopping session, reservation and payment attempt. Link them for tracing, but avoid using one token as authority for all five decisions.
The user interface should say that entry to the shop is not a guarantee of ticket availability. If the event sells out, the waiting room should report that state directly rather than keeping visitors waiting for an outcome that can no longer happen.
Choose a Fairness Policy Before a Data Structure
With first in, first out, an authoritative admission order reflects the sequence in which eligible visitors join. This rewards early arrival, but can encourage users and automated clients to connect long before the advertised sale time.
A pre-sale waiting period can collect visitors before opening and then assign their initial order randomly. Visitors arriving after that initial allocation join behind the existing group. This is a different policy, with different expectations about what arriving early means.
Cloudflare's queueing-method documentation provides examples of FIFO and random admission in a managed waiting-room service. The important architectural lesson is that the ordering mechanism should implement a deliberate product policy.
For this example, use a pre-sale group with a server-assigned initial order, followed by FIFO admission for later arrivals. Persist the sale phase and ordering decision so restarting a coordinator does not create a second lottery or rearrange everyone unexpectedly.
Never trust a client timestamp as proof of arrival. A visitor can change their clock or replay a request containing an earlier time. The server assigns the authoritative sequence or rank after accepting the join operation.
Decide how households, shared accounts and multiple devices behave. One entry per browser session is easy to support but permits several entries per person. One entry per authenticated account improves coordination but introduces login load and does not eliminate people creating multiple accounts.
Estimate the Arrival and Polling Workload
Suppose two hundred thousand visitors arrive over the minute before a sale opens. The join service must absorb more than three thousand requests per second on average during that minute, with a potentially sharper peak around the advertised time.
If one hundred thousand waiting browsers poll every five seconds, they generate twenty thousand status requests per second. Those requests must not reach the booking database. The waiting room needs a cheap status path that can scale independently of checkout.
Now suppose the booking system can safely support one thousand active shopping sessions and the average session lasts two minutes. A rough steady-state replenishment rate is 1,000 divided by 120, or about eight new shoppers per second.
That calculation is a planning estimate, not a guaranteed control setting. Session duration varies, active shoppers consume different resources and the slowest dependency may change during the sale. Keep headroom and use measurements from representative load tests.
The three rates are different: arrivals to the waiting room, status traffic while waiting and admission into the shop. The architecture should absorb a large difference between the first two workloads and the much smaller rate the purchase system can sustain.
Define the Data Model and API
A queue entry might contain an event ID, queue identity, optional account binding, server-assigned order, join time, last activity time and state. Store the policy version and sale phase needed to interpret its order.
An admission grant is a separate record with a unique identifier, queue entry, issue time, claim deadline and state. A shopping session records its activation and expiry. This gives the system a place to recognise a repeated grant claim without consuming another slot.
The API could expose a small set of operations:
POST /events/{eventId}/queue
GET /events/{eventId}/queue/status
POST /events/{eventId}/admission/claim
POST /events/{eventId}/shopping-session/renew
POST /events/{eventId}/shopping-session/leave
Derive the current queue identity from an authenticated session or a protected cookie rather than accepting arbitrary ownership claims in the request body. An opaque queue identifier alone should not allow another browser to steal an admission grant.
Join and claim operations need idempotent behaviour. If the server creates an entry but the response is lost, a retry should return that entry. If a grant has already been claimed by the same authorised session, repeating the claim should return the existing shopping session.
Keep status responses small. Return the queue state, a coarse progress indication, the next suggested poll time and any sale-wide announcement. Do not return a list of other visitors or expose internal identifiers that are unnecessary for the waiting experience.
A High-Level Architecture
The admission gate sits before the expensive application routes:
Visitor -> Edge Admission Gate -> Static Waiting Page
| |
| v
| Queue Status API
| |
| v
| Queue State Store
| ^
| |
| Admission Coordinator
| ^
| |
| Booking Health Signals
|
+-- valid shopping session --> Booking Application
|
Inventory and Payment
Edge Admission Gate
The gate validates the event and admission session before forwarding protected traffic. It can serve a cached waiting page without starting a full booking request. Public static assets should remain cheap to retrieve so the waiting experience does not depend on the overloaded application.
Queue Service
The Queue Service owns joining, identity recovery and status. It keeps the high-volume waiting traffic away from inventory. Partitioning by event provides a natural starting boundary because one sale's visitors do not need to share a total order with another sale.
Admission Coordinator
The coordinator decides how many visitors may enter and selects eligible queue entries. It must coordinate capacity ownership, rather than merely read a count and hope no other instance admits users at the same time.
Booking Application
The booking system validates admission, manages reservations and processes purchases. It reports useful health signals to the admission controller, but it remains authoritative for inventory and order state.
Make Joining and Refreshing Safe
Create the queue identity once and retain it in a protected session cookie or equivalent application session. A refresh submits the same identity and retrieves current state rather than creating another entry.
If two join requests arrive together, use a uniqueness rule for the chosen event-and-session scope. Both requests should converge on one queue entry. An early lookup alone is insufficient because concurrent requests can both observe that no entry exists.
Do not use an IP address as the only identity. Several legitimate visitors may share a household, workplace or mobile-network address. IP-based controls can contribute to abuse detection, but they should not silently merge unrelated customers into one queue position.
Recovery across devices needs a separate product decision. An authenticated account may recover its place after signing in elsewhere. An anonymous browser that loses its cookie may have no safe proof that it owns the original entry. Avoid promising recovery that the identity model cannot support.
Record the original rank separately from last activity. A visitor's status request should refresh their liveness signal without rewriting their arrival order. Otherwise, frequent polling can accidentally change fairness.
Model Queue and Admission State Explicitly
Use a state machine with transitions such as:
WAITING -> OFFERED -> ACTIVE -> COMPLETED
| |
v v
EXPIRED EXPIRED
WAITING -> LEFT
An OFFERED entry has a short window to claim admission. It is not yet equivalent to a visitor actively making inventory requests. Reserving capacity for offers prevents the system from promising the same future slot to many browsers.
The claim operation atomically checks the offer, its owner and its deadline, then activates the shopping session. Repeated claims return the existing result. A claim arriving after the offer expires follows the published policy, such as returning to a defined waiting state.
Keep the release operation idempotent. A checkout completion event, a browser leave request and an expiry worker may all try to release the same session. Only one state transition should return its capacity to the pool.
Do not depend on a browser close event for correctness. Browsers crash, mobile networks disappear and users simply abandon tabs. Expiry and reconciliation must eventually release capacity even when no explicit leave request arrives.
Control Both Concurrency and Admission Rate
The controller uses an active-session ceiling and an admission rate. The ceiling limits the amount of work that can be in progress; the rate prevents a large group of newly admitted shoppers hitting inventory at once.
Count offered grants as reserved capacity according to the policy. If the system ignores unclaimed offers, it can issue several waves of invitations before earlier visitors have time to claim them, creating a later surge.
Use short, bounded admission batches. A coordinator that admits all currently free capacity in one operation can create a synchronised burst even when the long-term average rate is acceptable.
Watch booking latency, error rates, connection-pool pressure and reservation failures. Increase admission gradually when the system is healthy, and reduce or pause it when the purchase path deteriorates. Avoid reacting to every individual slow request, which can make the controller oscillate.
Hysteresis and smoothing help distinguish sustained pressure from noise. For example, the controller might require several healthy observations before increasing the budget and respond more quickly to a serious checkout failure. Tune these rules with load tests rather than treating a queue length as the only signal.
Prevent Double Allocation Across Coordinators
Suppose two coordinators both read that one hundred slots remain. If each admits one hundred visitors, the event receives twice its intended allowance. The count must be coupled to an atomic allocation decision.
A single logical coordinator per event is a reasonable initial design. It can run on replaceable infrastructure while keeping durable state in a store that supports the required transaction or compare-and-swap operation.
If leadership uses a lease, a replacement coordinator must protect against a paused former leader resuming after its lease expires. A fencing generation checked by the authoritative allocation store can reject writes from an old leader. An expiry timestamp alone does not stop code that is already running elsewhere.
For larger deployments, allocate bounded budgets to regional gates. The sum of outstanding budgets must remain within the event's capacity policy. Unused budgets and expired grants need reconciliation before the controller assumes the capacity has returned.
Budget distribution trades tighter coordination for local speed. State the maximum possible overshoot and the recovery behaviour. A design that tolerates a small bounded excess should describe it explicitly rather than claim mathematically exact global capacity.
Keep Status Traffic Cheap and Polite
The waiting page can be a static asset served from a CDN. Its status endpoint reads queue state or a compact status cache, with no dependency on the booking database for every poll.
Return a suggested retry interval and add random variation to client polling. If every waiting browser wakes on the same five-second boundary, status traffic becomes a sequence of avoidable spikes.
Back off when the status service is under pressure. A temporarily stale progress estimate is preferable to taking down the service that preserves queue membership. The browser should retain its identity and show a clear reconnecting state while it waits.
Server-sent updates or persistent connections can reduce polling in some environments, but they introduce connection management and reconnect load. Compare their operational cost with a simple, cache-friendly polling model before adding them.
Use coarse position information where appropriate. Continuously calculating an exact count of everyone ahead of each visitor may require expensive global work. A rank range or estimated wait can be more scalable, provided the interface explains its uncertainty.
Explain Waiting-Time Estimates Honestly
A wait estimate depends on the number of eligible visitors ahead, the current admission rate and how quickly active shoppers leave. None of those values is perfectly stable throughout a sale.
If twelve thousand visitors are ahead and admission is twenty per second, the simplest estimate is ten minutes. That assumes the rate remains steady and that the ranking policy does not change. A payment-provider slowdown can make the estimate obsolete almost immediately.
Present a range or an approximate duration, and update it without implying a guarantee. Explain pauses in plain language. A queue that appears frozen without context encourages users to refresh, open more tabs and contact support.
Distinguish queue progress from inventory availability. Advancing to the front does not prove the preferred seat category remains in stock. Sale-wide announcements can report sold-out categories or an event-wide pause without making every status request query detailed inventory.
Avoid misleading progress animations that continue while admission is stopped. The waiting room is part of the product's trust relationship, so truthful degraded behaviour is more useful than an apparently smooth but inaccurate display.
Validate Admission on Every Protected Route
A signed admission token can include the event, grant identifier, session binding, issue time, expiry and signing-key identifier. The booking application verifies its integrity and intended audience before treating it as permission to enter the protected flow.
Do not let the client choose a different event or extend an expiry by changing token fields. Signing protects integrity, while server-side state or an appropriate revocation mechanism handles cases where a previously issued grant must no longer be accepted.
Protect more than the HTML purchase page. Inventory APIs, reservation endpoints and checkout routes can all create expensive work. A direct API call should face the same admission requirement as a request made through the normal page.
Keep admission validation distinct from account authorisation and anti-forgery protection. A valid queue token does not prove that a request can modify someone else's order. It is one gate in a larger set of business and security checks.
Restrict direct origin access where it would bypass the edge gate, or enforce the same admission check inside the application. The correctness requirement is that every relevant route reaches a trusted validation point.
Handle Shopping-Session Expiry Carefully
A shopping session should have a bounded lifetime so abandoned visitors do not occupy capacity forever. Renewal can be allowed while the user is actively making progress, subject to an absolute limit and the event's policy.
Protected routes must reject expired, released or superseded shopping sessions before allowing new shopping work. A valid signature alone does not prove that a session still owns capacity: once its slot is released, an old token must not let the browser continue using that slot alongside a newly admitted shopper.
Separate session expiry from a seat reservation's deadline. A shopper may enter the shop, browse for a while and only later create a short seat hold. Those timers represent different resources and should not overwrite each other.
Decide what happens when admission expires during an active payment attempt. The system may allow the already-started order workflow to finish while preventing new reservations. Blocking every callback or status request at the expired admission gate can make a valid purchase impossible to resolve.
Provider webhooks and trusted internal recovery jobs should have their own authentication path. They are not shoppers and should not need to queue to report an already initiated payment outcome.
After completing a purchase, release the shopping session according to policy. If the customer is allowed to make another purchase, decide whether they retain admission or rejoin. This is a product rule that affects both fairness and capacity estimates.
Recover from Failures Without Resetting the Queue
Queue state should survive replacing stateless API instances. A process restart must not make every waiting browser look like a new arrival or assign a fresh random order.
If the coordinator becomes unavailable, pause new admission while keeping status and membership available where possible. Existing valid shopping sessions can continue under the chosen policy. Preserving order is usually preferable to accepting uncoordinated new grants.
When the authoritative queue store is unavailable, decide how the edge behaves. Failing open may overwhelm the very booking service the waiting room exists to protect. A protected ticket sale will often need to stop new admission while serving a clear temporary waiting message.
After recovery, reconcile offered and active sessions against durable grants. Do not simply reset the active count to zero: many previously admitted customers may still be shopping. Reissuing their capacity would create an overshoot.
A regional failover needs the same ownership discipline as coordinator replacement. Only the authorised leader or budget owner should issue new grants, and the replacement must account for allocations that may have succeeded before the outage.
Prevent Abuse Without Breaking Legitimate Access
Apply rate limits to joining, claiming and status requests. Combine session identity, account signals and network behaviour rather than treating one IP address as one human.
Bot detection can operate around the queue, but it should not become the only correctness mechanism. A determined client that bypasses a visual challenge must still lack a valid admission grant for protected booking operations.
Do not allow replaying an old event token into a new sale. Bind grants to the event and sale generation, and rotate signing keys through a process that lets validators identify valid keys during a controlled transition.
Provide an accessible waiting page that works with assistive technology and does not require constant interaction to keep a place. People should not lose their position because they cannot respond to unnecessary animations or repeated manual refresh prompts.
Keep personal information out of queue URLs and logs where it is not needed. Operational teams generally need correlation identifiers and state transitions, not a publicly exposed list of who is waiting.
Observability and Load Testing
Monitor joins, waiting entries, outstanding offers, active sessions, claim failures and released capacity. Compare the coordinator's view with reconciled durable state so counter drift becomes visible.
Measure the oldest wait, abandonment and successful checkouts as well. These show whether visitors are making useful progress through the complete journey, rather than merely moving between queue states.
Record the operational mode alongside those counters. Normal admission, pausing everyone in the queue and intentionally bypassing the gate have very different implications. Cloudflare's status guidance illustrates distinct queueing, disabled and queue-all states. An empty queue must not be mistaken for healthy capacity when protection has actually been disabled.
Track admission rate alongside booking latency and error rate. A short queue does not prove the system is healthy if admitted shoppers are stuck at payment. Conversely, a long queue may be the intended result of safely protecting a constrained sale.
Test simultaneous joins, duplicate claims, expired offers, coordinator replacement and a slowdown in the booking database. Verify that capacity is neither allocated twice nor permanently lost after a crash.
Run a test where status traffic is much larger than checkout traffic. This catches accidental dependencies that send waiting-room requests into the same database pool used by active purchasers.
Finally, test a sale pause and a sold-out transition. The system should communicate the change, preserve or conclude queue state according to policy and stop issuing misleading new admission offers.
Present the Design and Consider Improvements
In an interview, start with the separation between waiting, admission and inventory. Then walk one visitor through joining, polling, claiming a grant, shopping and leaving. Explain which transitions are atomic and what happens if each response is lost.
Use the workload estimate to justify independent capacity for the waiting page and queue status. Explain why one hundred thousand waiting browsers can coexist with a much smaller number of active shoppers without sending all their traffic to inventory.
Further improvements could include adaptive regional budgets, better estimates based on session-duration distributions and stronger cross-device recovery for authenticated users. Each improvement should preserve the published fairness policy and the authority of the allocation store.
Avoid starting with a highly distributed exact global queue unless the requirements demand it. An event-scoped coordinator with clear failover and bounded admission may provide a simpler design that the team can explain and operate.
Summary
A virtual waiting room protects a constrained booking system by controlling admission before expensive work begins. It must preserve queue identity, implement an explicit fairness policy and coordinate both arrival rate and active-session capacity.
Durable grants, idempotent claims and recoverable expiry handling make refreshes and failures safe. Protected routes still validate admission, while inventory and payment retain their own authority over reservations and purchases.
The strongest design does more than show a position in a queue. It explains why a visitor keeps that position, how capacity is allocated once, what happens during a pause and why entry to the shop is never confused with ownership of a ticket.
