8. Ticketmaster (Event Ticket Booking) Advanced + High-Contention
A. Realistic Interview Prompt
This is the sharpest strong-consistency problem in this document. Every prior design leaned eventually-consistent almost everywhere, with at most one narrow atomic island (Rate Limiter's counter, Uber's driver assignment). Here, the entire product is built around a single unforgiving invariant — a seat can be sold at most once — combined with a traffic pattern that is the opposite of steady: near-zero demand, then an instantaneous, massive spike the second a popular on-sale opens. Both properties push hard against the "just scale it out and eventually-consistent everything" instincts most of the rest of this document teaches.
B. Difficulty
Advanced, and specifically high-contention — the naive design (J) isn't wrong about the data model, it's wrong about concurrency, and that flaw is invisible until two people click "buy" on the same seat within milliseconds of each other, which is precisely what happens at scale during a real on-sale.
C. What This Problem Tests
- Recognizing overselling as a check-then-act race — by now a familiar shape from Rate Limiter (N) and Uber (P), but here the stakes are a legal/financial commitment (a sold ticket), not an approximate count or a delayed match
- Judgment about when to lock a seat (a short-lived reservation hold) versus when to actually commit the sale (after payment succeeds) — a two-phase commitment problem, not a single atomic step
- Handling extreme, predictable traffic spikes (a scheduled on-sale time) differently from unpredictable organic load — a virtual waiting room is a deliberate admission-control strategy, not a workaround for a badly-built system
- Recognizing that strong consistency is required at the seat level but not necessarily at the "how many seats are left, roughly" display level — a narrower, more precise consistency split than any prior problem
- Comfort discussing bot/scalper mitigation as a real, product-relevant concern, not just an afterthought
D. Requirements
Functional
- Users can browse events and view seat availability
- Users can select one or more specific seats (or general-admission quantity) and reserve them temporarily while completing checkout
- Users can complete payment to convert a temporary reservation into a confirmed, sold ticket
- A reservation that isn't completed within a time limit is released back to availability
Non-Functional
- No overselling, ever — exactly one confirmed sale per seat; this is the single non-negotiable correctness property of the entire system
- Survives extreme, spiky load — this document uses a 50,000-seat venue's on-sale generating 1M+ concurrent requests in the first few seconds as the standard illustrative worst case, the kind of load pattern popular real-world on-sales are known to produce
- Fast checkout — the reservation step itself should resolve in a low number of hundreds of milliseconds; a slow "is this seat available" check directly worsens contention by widening the race window
- Fairness — under contention, some reasonably fair ordering (not strictly guaranteed first-come-first-served, but not arbitrary either) should determine who gets a shot at limited inventory
Out of scope: event creation/venue-management tooling, pricing/dynamic pricing, refunds/resale marketplace.
E. Clarifying Questions
| # | Question | Why It Matters | Design Decision Affected |
|---|---|---|---|
| 1 | Specific seat selection, or general-admission quantity-only? | Specific-seat selection is a strictly harder concurrency problem — many individually contested resources instead of one decrementing counter | Core Entities (H) |
| 2 | How long should a seat be held once selected, before checkout must complete? | Directly sets the reservation-hold TTL, a central parameter of the whole design | Deep Dive 2 (N) |
| 3 | Is the traffic pattern a known, scheduled on-sale time, or unpredictable organic demand? | A known on-sale time is exactly what a virtual waiting room is designed to defend against — a materially different mitigation than general rate limiting | Deep Dive 4 (P) |
| 4 | Should displayed seat-availability counts be exact in real time, or is a brief lag acceptable? | Exact-in-real-time display would require the same strong consistency as the sale itself; a brief lag decouples display load from the critical reservation path | Deep Dive 6 (R) |
| 5 | Do we need to defend against bots/scalpers buying disproportionate inventory? | A real, product-relevant concern for high-demand events, requiring active countermeasures beyond basic rate limiting | Deep Dive 7 (S) |
F. Assumptions
- Confirmed: browse → select seats → temporarily reserve → pay → confirm, with automatic release on hold expiry.
- Assumption: specific reserved-seating selection (E's clarifying question 1) — the harder, more interesting, and more commonly-discussed version of this problem versus pure general-admission quantity.
- Assumption: a short reservation hold (illustrative: 5-10 minutes) between seat selection and payment completion — long enough for a real checkout flow, short enough to bound how long a seat can be tied up by an abandoned cart.
- Assumption: the on-sale traffic pattern is a known, scheduled spike (E's clarifying question 3) — the standard, most product-realistic framing, and what makes the virtual waiting room (P) a first-class deep dive rather than a footnote.
- Assumption: displayed seat-availability counts tolerate a brief lag (E's clarifying question 4) — this single assumption is what lets the display/browse path be decoupled from the strongly-consistent reservation path.
G. Capacity Estimation
| Metric | Value / Calculation | Why It Matters |
|---|---|---|
| Venue size (illustrative on-sale) | 50,000 seats | The actual number of contested resources — small relative to the request volume competing for them |
| Concurrent requests at on-sale open | 1M+ within the first few seconds (illustrative, matching known real-world popular on-sale behavior) | A ~20:1 (or far higher) ratio of demanding requests to available seats — the core scale challenge is contention, not steady-state throughput |
| Reservation hold duration | 5-10 minutes (F's assumption) | Bounds how long an abandoned reservation ties up a seat before automatic release (N) |
| Peak reservation-attempt QPS | A large fraction of the 1M concurrent requests will attempt a reservation within seconds — tens of thousands of reservation attempts/sec, illustratively, against only 50,000 total seats | Confirms the reservation step itself, not the browse/display path, is the system's true bottleneck and highest-risk component |
| Seat/inventory data size | 50,000 seats × ~200 bytes (seat_id, section, status, hold expiry) ≈ ~10MB per event | Trivially small — confirms this is a concurrency problem, not a storage-volume problem, unlike almost every other problem in this document |
| Events at this scale/year | A modest number of "hot" on-sales/year across a platform, versus a much larger long tail of low-contention events | Justifies treating extreme contention as a per-event, on-demand mode (P) rather than the platform's permanent steady state |
H. Core Entities
| Entity | Purpose | Key Fields |
|---|---|---|
Event | A ticketed event at a venue | event_id, venue_id, on_sale_at |
Seat | One specific, individually contested seat for one event | seat_id, event_id, status (available/held/sold), hold_expires_at, held_by |
Reservation (ephemeral) | A user's in-progress hold on one or more seats | reservation_id, user_id, seat_ids, expires_at, status |
Order | A confirmed, paid purchase | order_id, user_id, seat_ids, payment_ref, confirmed_at |
I. API Design
GET /v1/events/{id}/seats -> [{ seat_id, status (approx, R) }]
POST /v1/events/{id}/reservations { seat_ids } -> { reservation_id, expires_at } (atomic hold, M)
POST /v1/reservations/{id}/confirm { payment_token } -> { order_id } (Q)
DELETE /v1/reservations/{id} (explicit early release)
POST /v1/events/{id}/waiting-room/join -> { queue_token } (P, only for flagged high-demand events)
Why a distinct Reservation resource between "browsing" and "confirmed order," not a direct seat-to-order purchase call: payment processing itself takes real time and can fail for reasons unrelated to seat availability (a declined card) — collapsing "hold the seat" and "charge the card" into one step would mean either holding the seat for an unpredictably long time during payment processing, or risking the seat being sold to someone else mid-payment. Splitting them into an atomic hold (M) followed by a separate confirm step (Q) is what makes both halves individually tractable.
J. Basic / Naive High-Level Design (V1)
The simplest thing that satisfies the functional requirement in isolation: a seats table with a status column; reserving a seat means reading its status, checking it's available, then updating it to held — as two separate database calls.
UPDATE status = held" --> DB
V1 — a two-step read-then-write. Correct-looking, but the gap between steps 1 and 2 is exactly where two users can collide.
Why this breaks immediately, not eventually: at real on-sale contention (G's 1M+ concurrent requests against 50,000 seats), the gap between "read status" and "write status" — even a few milliseconds — is a window where two different requests for the same seat can both read "available" and both proceed to write "held," resulting in two users believing they've reserved the identical seat. This is the exact check-then-act race named explicitly in Rate Limiter (N) and Uber (P), just with a legally/financially binding outcome instead of an approximate count or a delayed match — and unlike those two problems, there is no acceptable "eventual" fallback here; a double-sold seat is a direct, visible, and expensive product failure.
K. Data Model & Store Choice
Why a relational database, not an eventually-consistent NoSQL store, for seat inventory specifically: this is the one entity in the entire document where ACID transactional guarantees and row-level locking are exactly the right tool, not an unnecessary constraint — a relational database's native support for a single atomic conditional update (UPDATE ... WHERE status='available', checking the affected-row count) or a short transaction with a row lock directly implements the correctness this problem needs, without inventing bespoke atomicity machinery the way Rate Limiter's Lua script or Uber's CAS had to.
Sharding by event_id: all seats for a given event are always accessed together (browsing, reserving, holding) and never need to be joined against another event's seats — a natural, clean shard key that also happens to contain each hot event's contention to its own shard, so one viral on-sale's load doesn't spill over onto the rest of the platform's traffic.
Reservation hold TTLs: tracked either as an expiry timestamp column checked lazily on read, or via a fast in-memory store (Redis) with native key expiry for immediate, proactive release — a hybrid is common in practice: the relational row is the source of truth for the seat's actual status, while a TTL-based cache entry drives prompt automatic release without needing a polling job.
L. End-to-End Flow — Seat Reservation to Confirmed Order
M. Deep Dive 1 — Preventing Overselling
Requirement this addresses: directly fixes V1's fatal flaw (J) — the single highest-signal deep dive in this entire problem.
The race, explicitly: two requests for the same seat arrive within milliseconds; a naive two-call read-then-write lets both believe they succeeded. The fix must collapse "check" and "act" into one indivisible operation.
| Mechanism | How | Trade-off |
|---|---|---|
| Atomic conditional update (chosen default) | UPDATE seats SET status='held' WHERE seat_id=? AND status='available', checking the affected-row count — the database's own row-level locking makes the check-and-write indivisible | Simple, uses standard relational-database guarantees directly, no bespoke coordination primitive needed — the most defensible default answer |
Pessimistic row lock (SELECT ... FOR UPDATE) | Explicitly lock the row for the duration of a transaction spanning the check and the write | Equally correct, but holds a lock for longer, which can worsen contention under extreme load if the transaction does anything slow inside it |
| Distributed lock (e.g. Redis-based) | Acquire an external lock keyed by seat_id before touching the database at all | Useful if the "hold" logic spans multiple systems, but adds an entirely new failure mode (lock-service unavailability) for a problem the database's own atomicity already solves cleanly |
Why the plain atomic conditional update is the right default, not the distributed lock: the same principle as choosing a Lua script over an external coordination service in Rate Limiter (N) — reach for the storage layer's own native atomicity primitive first; only introduce an external distributed lock if the operation genuinely needs to span systems the database transaction can't reach, which a single seat's status update does not.
Multi-seat reservations specifically: reserving several seats at once (a group of friends booking together) must succeed or fail as a single unit — wrapping the conditional updates for all requested seats in one database transaction ensures a user is never left holding 3 of 4 requested seats because one was contested away mid-request.
N. Deep Dive 2 — Reservation Holds & Expiry
Requirement this addresses: the temporary-hold functional requirement (D) — a seat marked "held" that's never released because a user abandoned checkout would slowly starve inventory even for a moderately popular event.
Why a bounded, automatically-expiring hold, not an indefinite one: per F's assumption, holds last 5-10 minutes — long enough for a real checkout flow, short enough that an abandoned cart doesn't meaningfully impact overall availability. Two release triggers work together: explicit release (the user backs out or a payment fails, per L) and automatic expiry (the TTL simply elapses with no confirmation).
Why a TTL-driven release (K's Redis-backed expiry), not a polling job scanning for expired holds: a periodic sweep introduces both latency (a seat might sit incorrectly "held" for up to one sweep interval past its actual expiry) and unnecessary database load scanning rows that are, most of the time, not yet expired. Native key expiry triggers the release essentially immediately when the TTL elapses, without any polling overhead — a materially better fit for a time-bounded resource than a scheduled scan.
O. Deep Dive 3 — Surviving Flash-Sale-Scale Load
Requirement this addresses: the extreme-spike NFR from D and G — 1M+ concurrent requests against 50,000 seats is a fundamentally different load shape than this document's other read-heavy problems, where load is large but comparatively smooth.
Why this isn't primarily a "scale out more servers" problem: the bottleneck isn't application-tier compute, it's contention on a small, fixed set of database rows — adding more booking-service instances just means more concurrent attempts to conditionally update the same 50,000 rows, which doesn't by itself reduce collisions, and can actually increase database-side lock contention if unbounded.
- Sharding by event (K) contains one event's contention to its own database shard, so a viral on-sale doesn't degrade unrelated events' booking flows.
- Read-path caching for browse/display (R) keeps the vastly larger "just looking" traffic off the strongly-consistent reservation path entirely — only an actual reservation attempt needs to touch the seat table's real-time state.
- Admission control at the edge (P) — deliberately limiting how many requests are even allowed to attempt a reservation concurrently, rather than letting all 1M+ requests hit the database layer simultaneously.
P. Deep Dive 4 — The Virtual Waiting Room
Requirement this addresses: directly extends O — a deliberate admission-control strategy specific to known, scheduled traffic spikes (E's clarifying question 3), distinct from general rate limiting (which reacts to load, rather than anticipating it).
at on-sale time"]) --> WR{"Virtual Waiting Room"} WR -- "assign queue position/token" --> Hold["Hold in queue
(polling or WebSocket for position updates)"] Hold -- "admitted in controlled batches" --> Booking["Booking flow (M)"] WR -.-> Reject["No booking-service load
from users still queued"]
The waiting room's entire job is to prevent the 1M+ concurrent surge from ever reaching the contested booking path at once.
Why this is a deliberate design choice, not a workaround for an under-scaled system: even a perfectly-scaled booking service still has only 50,000 seats to sell — admitting all 1M+ simultaneous requests into the reservation flow at once wouldn't let more people successfully book, it would just mean ~950,000 requests fail the atomic conditional update (M) after having already consumed real capacity getting there. A waiting room controls the rate of admission into the contested path, smoothing the exact same total demand into a shape the reservation system can process without the database layer being hit by the full spike instantaneously.
Fairness mechanism: typically a randomized or arrival-order queue token issued at (or shortly before) the on-sale time, not raw first-byte-received ordering — raw network-arrival-order ordering would systematically favor users with lower latency/better connections or automated tooling, which directly motivates naming this alongside the bot-mitigation discussion in S.
Q. Deep Dive 5 — Payment Integration Timing
Requirement this addresses: the confirm-purchase functional requirement (D) — the second half of the two-phase commitment introduced in I's API design.
Why payment happens strictly after the seat hold succeeds, never before or concurrently: charging a payment before confirming the seat is actually available risks charging a user for a seat that gets contested away before the charge even completes; attempting both simultaneously reintroduces exactly the kind of race M was built to eliminate. Sequencing them — hold first (fast, purely internal), charge second (slower, involves an external payment processor) — means the slow, externally-dependent step only ever happens once the seat is already safely, atomically held under that user's reservation.
Idempotency on the confirm step: a client retrying a confirm call after a timeout (without knowing whether the first attempt actually succeeded) must not risk double-charging the user — the confirm endpoint accepts an idempotency key so a retried request with the same key returns the original result rather than re-attempting the charge, the same idempotent-retry principle used for client_msg_id in the Chat System problem, here protecting a financial transaction instead of a chat message.
R. Deep Dive 6 — Seat Map Display & Search
Requirement this addresses: the browse functional requirement (D), under the approximate-display assumption from F — this is the read path, and it must be decoupled from the strongly-consistent reservation path (M) to keep the vastly larger "just browsing" traffic from ever touching the contested seat rows.
Why the displayed seat map can lag reality slightly: per E's clarifying question 4, a seat map that's a few seconds stale (occasionally showing a seat as available that was actually just reserved by someone else) is a minor, self-correcting UX hiccup — the user simply gets a "sorry, that seat was just taken" response from the real, strongly-consistent check in M when they actually attempt to reserve it. A cache (Redis, or a read replica of the seat table) serving the browse/seat-map view absorbs the overwhelming majority of read traffic without ever touching the primary seat table that M's atomic updates depend on.
Search/discovery (events by artist, venue, date) is a standard, fully separable search-index concern — the same async-indexing pattern used for search in the News Feed, Chat System, and Video Streaming problems, entirely decoupled from seat-level inventory state.
S. Deep Dive 7 — Fairness & Bot/Scalper Mitigation
Requirement this addresses: the fairness NFR from D — a real, product-relevant concern for high-demand events, where automated tools attempting to buy disproportionate inventory directly undermines the fairness the waiting room (P) is trying to provide.
- CAPTCHA or equivalent human-verification at waiting-room entry — raises the cost of large-scale automated queue-joining without meaningfully inconveniencing genuine individual buyers.
- Per-account/per-payment-method purchase limits — bounding how many tickets a single identity can acquire for one event, enforced as an additional check alongside (not instead of) the seat-level atomic hold in M.
- Rate limiting reservation attempts per user/IP — a direct application of the Rate Limiter problem's own design (token bucket or sliding-window counter) layered on top of this system, reused rather than reinvented.
Why this is worth naming even though it's not the problem's core mechanic: a design that flawlessly prevents overselling but has no defense against a single automated actor acquiring a large fraction of available seats has technically satisfied D's correctness requirement while missing the fairness requirement entirely — worth stating explicitly as a distinct, product-relevant concern rather than assuming "no double-booking" is the whole story.
T. Full Solution at Scale — Final Architecture
served from cache/replica"] U -- "on-sale traffic" --> WR["Virtual Waiting Room (P)"] WR -- "admitted, rate-controlled" --> BS["Booking Service"] subgraph SEATDB [" Sharded Seat DB, by event_id (K) "] direction LR Shard1[("Event A shard")] Shard2[("Event B shard")] end BS -- "atomic conditional update (M)" --> SEATDB BS -- "hold TTL (N)" --> TTLStore[("Redis TTL Store")] BS -- "hold confirmed" --> Pay["Payment Service (Q)
idempotent charge"] Pay -- "success" --> SEATDB Pay -.-> Order[("Order Store")] BS -.-> Guard["Bot/Fairness Guard (S)
rate limits + purchase caps"]
Everything above is the composition of M through S — no new component introduced here.
U. Design Evolution
- V1 — two-call read-then-write seat status (J): broken under any real concurrent contention.
- V2 — atomic conditional update / row-level locking: eliminates the overselling race, the single most important fix in the design (M).
- V3 — TTL-bound reservation holds: prevents abandoned checkouts from starving inventory indefinitely (N).
- V4 — sharding by event_id: contains one hot event's contention to its own shard (O).
- V5 — virtual waiting room for known high-demand on-sales: controls admission rate into the contested path instead of absorbing the full spike directly (P).
- V6 — sequenced hold-then-charge with idempotent confirmation: makes payment safe against races and retries (Q).
- V7 — cached/replica-backed browse path: removes the vastly larger "just looking" traffic from the strongly-consistent reservation path (R).
- V8 — purchase limits + bot mitigation layered alongside the atomic hold: extends correctness (no overselling) into actual fairness (S).
What stays stable throughout: the reserve → confirm two-step contract from I — every version underneath changes without the client needing to change its checkout flow shape.
V. Consistency Model
The most strongly-consistency-weighted design in this entire document: the seat-hold step (M) requires genuine strong consistency — there is no acceptable eventual-consistency fallback for "is this seat available," unlike every prior problem's narrower strong-consistency islands. The browse/seat-map display (R) is the one deliberately eventually-consistent piece, explicitly to keep the much larger read volume off the strongly-consistent path — a brief display staleness is harmless because the real check happens at reservation time regardless of what the display showed. This is close to the inverse of URL Shortener's model (eventual everywhere, one narrow strong-consistency island for uniqueness) — here strong consistency is the default for anything touching actual inventory, and eventual consistency is the narrow, deliberate exception carved out only for display.
W. Failure Scenarios
- Two reservation attempts race for the same seat: the atomic conditional update (M) ensures exactly one succeeds; the loser gets an immediate, clear "unavailable" response rather than a false success.
- A user abandons checkout after holding a seat: TTL expiry (N) releases it automatically within the bounded hold window — no seat is lost to an abandoned cart indefinitely.
- Payment fails after a successful hold: the seat is explicitly released back to available immediately (L), rather than waiting out the full TTL unnecessarily.
- A confirm request is retried after a timeout (client never received the original response): the idempotency key (Q) ensures the payment isn't charged twice and the same order result is returned.
- One event's on-sale goes viral far beyond capacity planning: sharding (O) contains the impact to that event's own shard; the waiting room (P) throttles admission rather than letting the surge hit the database directly.
X. Bottlenecks & Scaling Boundary
| Stage | Actual Bottleneck | Fix Applied |
|---|---|---|
| V1 two-call read-then-write | A check-then-act race allows overselling under any real concurrency | Atomic conditional update / row lock (M) |
| Atomic updates, no shard isolation | One viral event's contention can degrade booking for unrelated events sharing infrastructure | Sharding by event_id (K, O) |
| Sharded, but full surge hits the DB directly | 1M+ concurrent requests overwhelming a shard even with atomicity in place | Virtual waiting room admission control (P) |
| Controlled admission, but browse traffic still hits the seat table | The much larger "just looking" volume competing for the same rows as real reservation attempts | Cached/replica-backed browse path (R) |
Y. Design Trade-Offs
| Decision | Option A | Option B | Trade-off |
|---|---|---|---|
| Overselling prevention | Application-level distributed lock | Database atomic conditional update (chosen) | A distributed lock adds a whole new failure mode for a guarantee the database's own transactional semantics already provide cleanly and more simply |
| Hold release | Periodic polling sweep for expired holds | TTL-driven expiry (chosen) | Polling is simpler to reason about but adds latency and unnecessary scan load; TTL expiry releases almost immediately with no polling overhead |
| Extreme-spike handling | Scale booking-service compute only | Virtual waiting room admission control (chosen) | More compute doesn't reduce contention on a fixed set of rows; a waiting room controls the rate of admission into the contested path directly, which compute scaling alone cannot do |
| Payment sequencing | Charge before/alongside the hold | Hold first, then charge (chosen) | Charging before the hold risks charging for a seat that gets contested away; sequencing makes the slow, external payment step happen only once the seat is already safely held |
| Seat-map display | Read directly from the live seat table | Cached/replica-backed, slightly stale (chosen) | Reading live guarantees perfect accuracy but puts the largest traffic volume on the most contended table; a cached view accepts brief staleness to fully decouple the two paths |
Z. Common Bad Designs
- A two-call read-then-write for seat status. Works in a demo with no concurrency; is the exact race that causes real-world overselling incidents — the single most important mistake to avoid on this problem.
- Treating this as an eventually-consistent problem "like everything else" in a typical scale-out system. Misses the one place in the design where strong consistency is genuinely mandatory, not optional.
- Charging payment before or concurrently with holding the seat. Risks charging a user for a seat that's no longer actually available by the time the charge completes.
- Treating extreme on-sale spikes purely as a "scale out more servers" problem. More application-tier compute doesn't reduce contention on a small, fixed set of database rows — admission control (the waiting room) addresses a different bottleneck entirely.
- No automatic hold expiry, or an indefinite hold. Lets abandoned checkouts silently starve inventory for a popular event over time.
AA. Interviewer Follow-Ups
| Follow-Up | Design Change | Components Changed | Components Unchanged |
|---|---|---|---|
| "Two users reserve overlapping seats in a group booking at the exact same moment." | Directly exercises M's transaction-wrapped multi-seat atomic update — succeeds or fails as one unit | None — this is exactly what M already handles | Everything |
| "Support general-admission (quantity-only, no specific seat) tickets too." | The contested resource becomes a decrementing counter rather than N individual seat rows — the same atomic-conditional-update principle applies, just against a count instead of a row per seat | Inventory model in H/K | Waiting room, payment sequencing, hold TTLs |
| "How do you stop scalper bots from buying half the venue?" | Directly exercises S's purchase-limit and bot-mitigation layer | None — this is exactly what S already handles | Core reservation/payment flow |
| "Add a resale/secondary marketplace." | A meaningfully new sub-system (re-listing a sold ticket) built on top of the existing Order entity, out of this design's core scope per D | New marketplace service consuming Order data | Primary sale flow entirely |
| "The waiting room itself is getting hammered at on-sale time." | The waiting room's own admission-token issuance is a much simpler, higher-throughput operation than an actual seat reservation — scale it independently, since it doesn't touch the contested seat rows at all | Waiting-room service scaling (P) | Seat DB, atomicity guarantees |
AB. Interviewer Evaluation Rubric
- Weak candidate: "check if the seat is available, then mark it reserved" with no mention of concurrency at all; no distinction between holding and confirming a purchase.
- Mid-level: recognizes the overselling race and proposes locking of some kind, but hasn't separated the hold step from the payment step, or considered what happens to abandoned holds.
- Senior: proactively designs the atomic conditional-update mechanism and the two-phase hold-then-confirm flow unprompted; introduces TTL-based automatic release; explicitly separates the strongly-consistent reservation path from the eventually-consistent browse path.
- Staff+: treats the extreme, scheduled-spike traffic pattern as requiring deliberate admission control (a virtual waiting room), not just more compute; raises fairness and bot mitigation unprompted as a distinct concern from pure correctness; reasons concretely about sharding by event to contain contention.
AC. Interview Simulation
AD. Online Solution Comparison
| Source | Approach | Strength | Weakness | Worth Learning |
|---|---|---|---|---|
| Hello Interview | Atomic-reservation-first framing, explicit two-phase hold/confirm discussion, staff-level virtual-waiting-room treatment | Clearest articulation of the overselling race and the hold/confirm split as the two central ideas — the core structure adopted for this section | Lighter on the specific bot/fairness mitigation angle | The two-phase commit framing (hold, then charge) reused directly as the spine of L and Q |
| ByteByteGo | Detailed database-locking mechanism comparison (optimistic vs. pessimistic) and sharding-by-event discussion | The most rigorous side-by-side treatment of locking strategies of any source reviewed | Less depth on the waiting-room's fairness-token mechanics specifically | The pessimistic-lock-vs-conditional-update comparison table adapted into M |
| donnemartin/system-design-primer (GitHub, general concurrency patterns) | General distributed-locking and race-condition-avoidance patterns applicable directly to high-contention inventory problems | Good grounding in the general check-then-act race vocabulary reused consistently across this document (Rate Limiter, Uber, here) | Not ticket-booking-specific — requires translating general concurrency advice into the seat-inventory domain | Confirming the check-then-act race is a recurring, well-known distributed-systems pattern, not a problem-specific quirk |
| Real-world virtual waiting room vendors (e.g. Queue-it — industry practice, reviewed at a conceptual level) | Actual production admission-control services used by real ticketing platforms during high-demand on-sales | Ground-truth confirmation that a dedicated waiting-room layer is standard real-world practice, not a hypothetical interview construct | Vendor marketing material, not a system-design walkthrough — needs translation, which P provides | Confirming that admission control as a deliberate, separate layer (not just rate limiting) is how real platforms handle exactly this problem |
AE. Interview Cheat Sheet
| Problem | Ticketmaster (Event Ticket Booking) |
| Difficulty | Advanced + High-Contention (the sharpest strong-consistency requirement in this document) |
| Core requirements | Browse, reserve, pay, auto-release; no overselling ever; 50K-seat venue, 1M+ concurrent on-sale requests, fast checkout, fair admission |
| Central decision | Atomic conditional update / row-level lock for the seat hold — the database's own transactional guarantees, not a bespoke or external lock |
| Purchase flow | Two-phase: atomic hold first, idempotent payment charge second — never charge before or during the hold |
| Hold expiry | TTL-driven automatic release (no polling), bounded to a short checkout window |
| Extreme spike handling | Virtual waiting room as deliberate admission control — not a workaround, since more compute can't create more seats |
| Browse/display | Cached/replica-backed, deliberately eventually consistent — kept off the strongly-consistent reservation path entirely |
| Fairness | Human-verification + per-account purchase limits + rate limiting, layered alongside (not instead of) seat-level atomicity |
| Common mistakes | Two-call check-then-write races; charging before holding; treating spikes as a pure compute-scaling problem; no hold expiry; ignoring fairness/bots |
| What NOT to over-engineer | A full resale/secondary marketplace or dynamic pricing engine — name them as separate, out-of-scope systems built on top of the Order entity |