Review: Ticketmaster
The hard part of a ticket sale is scarce inventory under a burst, not drawing a payment box.
Clarify this prompt before choosing components
Ask the highest-impact questions first, agree on priorities, and record the interviewer’s answers. If a detail is unspecified, state an assumption and explain which decision depends on it. The numbers in this worked lesson are exercise assumptions, not company facts.
| Ask the interviewer | Why the answer changes the design |
|---|---|
| Assigned seats, general admission or both? | Seat ownership and counted-capacity invariants differ. |
| How long are holds valid, and can payment finish after expiry? | Fence late confirmations against the current hold generation. |
| Peak on-sale traffic and required queue fairness? | Admission, anti-bot policy and overload behavior shape the design. |
Use the Interview Playbook for the shared 45-minute sequence and fresher, intermediate, senior and staff evidence. Start with one complete operation. Extend the answer with the failure/recovery traces below, then defend a workload-specific alternative and a measurable rollout. Scope and evidence matter more than a large component list.
Ticketmaster: Understand the building blocks
Choose a mechanism because it satisfies a requirement. These explanations describe the exercise design, not undisclosed implementation choices at the named company.
Ticketmaster: Quick vocabulary
| Term | Meaning and role in this exercise |
|---|---|
| API: Application Programming Interface | The contract for requests, responses and errors between components. An API route does not itself enforce every business invariant. |
| TTL: Time to Live | An entry/grant lifetime. Logical expiry, physical cleanup and urgent revocation are distinct conditions. |
| PK: Primary Key | The declared row identity in a database schema; it does not automatically enforce every cross-row business rule. |
Ticketmaster: Relational database transaction and constraints
What it means: A relational store keeps structured rows; a transaction commits a bounded group of changes together. Constraints reject specified invalid states.
Why this problem needs it: An all-or-nothing seat set needs one enforcing transaction.
How to use it: Use primary/unique keys and a chosen isolation/locking protocol for the actual invariant. Atomically persist related rows and the operation result before acknowledgement.
Example in this design: Requests for S1,S2 and S2,S3 overlap on S2; one atomic set transition wins.
When it fits: Useful for bounded cross-row correctness, joins and access paths that fit the relational model.
Alternative and limits: Key/value stores can fit simpler keyed operations. “SQL” alone does not prevent lost updates, validate every cross-row rule or coordinate an external provider.
Ticketmaster: Lease, ownership epoch and fencing
What it means: A lease grants temporary work ownership. A fencing token or epoch identifies a newer owner so stale owners can be rejected at the mutation boundary.
Why this problem needs it: A hold expires, but an old release must not free a later owner’s seat.
How to use it: Claim a job under an atomic authority, increment its attempt/epoch, and require that value on result publication. The protected resource must enforce the check.
Example in this design: Release only the current hold ID/version, never merely by seat ID.
When it fits: Useful when workers can pause, restart or overlap after a timeout.
Alternative and limits: A non-expiring lock risks blocking forever. Lease expiry alone cannot stop an old process that resumes; external effects need their own supported idempotency/reconciliation.
Ticketmaster: Saga and compensation
What it means: A saga coordinates several separately committed business steps. Compensation is an explicit corrective business action after a partial outcome.
Why this problem needs it: Seat booking and a payment provider commit separately.
How to use it: Track each step and its stable identity; on failure reconcile uncertain outcomes before requesting an allowed compensating action.
Example in this design: An uncertain charge is reconciled against the original payment identity before compensation.
When it fits: Useful when inventory, payment and dispatch have independent authorities.
Alternative and limits: A local transaction is simpler when all required state shares one database. Compensation may be partial or impossible; it is not automatic transaction rollback.
Ticketmaster: Durable work queue and backpressure
What it means: A queue separates accepted work from execution. Backpressure limits admission or consumption when downstream capacity is finite.
Why this problem needs it: A waiting room can bound admitted purchase attempts during a burst.
How to use it: Persist a stable job identity, consume with bounded concurrency, acknowledge after the required durable transition, and cap retries/backlog according to policy.
Example in this design: Admit at the inventory/payment safe rate; queue position itself does not reserve a seat.
When it fits: Useful for asynchronous work that must survive worker failure or absorb a finite burst.
Alternative and limits: A synchronous call is simpler for short bounded work. A queue does not create capacity, strict global ordering or exactly-once external effects.
Interview scope and guarantees
Browse events, view approximate seat availability, hold specific seats, and buy the held seats. The invariant is that one seat has at most one completed sale. Browsing may be stale, but holding must serialize conflicting buyers. Agree a hold duration and a clear response for expired holds.
Capacity worksheet
Assume 100,000 buyers arrive in one minute for 10,000 seats: roughly 1,667 admissions/s average before retries. If each user refreshes 5 times, reads reach 8,333/s. The contention is concentrated on one event, so adding database shards for other events does not repair the hot event.
Concrete API contract
POST /events/{id}/holds {seatIds} -> {holdId,expiresAt}
POST /holds/{id}/purchase {paymentToken} -> 202 {orderId}
GET /orders/{id} -> {state}
Use a stable purchase key across retries.Data model and access paths
seats(event_id, seat_id, state, hold_id, expires_at, version); PK(event_id,seat_id)
holds(hold_id PK, owner_id, state, expires_at)
orders(order_id PK, hold_id UNIQUE, payment_ref UNIQUE, state)Evolve a solution and explain each change
Read the diagram
Model one seat / Give every seat an authoritative ownership row and conditional state transitions. / Checking availability before writing permits two winners.; Hold before payment / Reserve with a hold ID and deadline; confirmation checks that same hold. / Late payment can arrive after expiration and reassignment.; Control the admission rate / Use a waiting room and bounded checkout traffic while retaining the database decision. / A queue or cache ticket does not itself own a seat.
Step 1: Model one seat
Give every seat an authoritative ownership row and conditional state transitions. Checking availability before writing permits two winners.
Step 2: Hold before payment
Reserve with a hold ID and deadline; confirmation checks that same hold. Late payment can arrive after expiration and reassignment.
Step 3: Control the admission rate
Use a waiting room and bounded checkout traffic while retaining the database decision. A queue or cache ticket does not itself own a seat.
Responsibility overview
Read the diagram
Buyer to Checkout API: request seat hold; Checkout API to Seat DB: conditional ownership; Payment provider to Checkout API: payment outcome; Seat DB to Reconciler: late outcome to repair; Admission queue to Checkout API: bounded checkout; Reconciler to Seat DB: reconcile matching hold
Worked end-to-end scenario
Two buyers select seat A12. The first transaction changes available to held with hold h1; the second receives unavailable. Buyer one starts payment, but h1 expires before confirmation. A later buyer holds the seat as h2. When h1 receives a successful payment callback, its confirmation must check current hold identity and state. It cannot convert h2 to sold. Record the late payment and reconcile by refund or another business-approved outcome. A browser countdown is useful feedback, but the server deadline is authoritative. Repeating the callback returns the original reconciliation result rather than issuing multiple refunds.
Why these access paths matter
Use event-plus-seat as the ownership key and hold ID as a confirmation condition. Index pending holds by expiry for bounded cleanup. The event availability projection can lag, but checkout must use the authoritative row. A single popular event can dominate traffic; shard independent seats only when the cart contract permits multi-seat reservations across that boundary.
Build the baseline first
Read the diagram
Buyer → hold API → seat transaction; Hold owner → purchase workflow → payment provider; Verified payment → seat sale commit → receipt
- Buyer → hold API → seat transaction.
- Hold owner → purchase workflow → payment provider.
- Verified payment → seat sale commit → receipt.
Evolve the design under load
Read the diagram
Waiting room → bounded admissions → hold API; Seat projection → cache → browsing clients; Expiry reconciler → conditional release of old holds
- Waiting room → bounded admissions → hold API.
- Seat projection → cache → browsing clients.
- Expiry reconciler → conditional release of old holds.
Defend the hardest decision
For adjacent seats, acquire locks in a stable seat order and commit all requested seats or none. An atomic conditional update checks available state or a safely expired hold. Expiry is validated with authoritative time during acquisition; a background sweeper is cleanup, not the correctness mechanism. Purchase must verify ownership, hold state, and the permitted payment deadline. A distributed cache lock alone cannot guarantee no double sale after failover.
Failure and recovery analysis
A payment succeeds after the hold has expired and been reallocated. Do not reclaim the seat from the new buyer. Persist the late payment as a reconciliation case and refund or offer alternatives. To reduce this window, define a bounded payment-in-progress extension before contacting the provider. Never hold a database transaction open across a provider network call.
Security and privacy boundary
Authenticate hold owners, limit bot traffic and hold creation, and keep payment credentials at the payment provider.
Interview follow-ups with reasoning
Question: How do you prevent a waiting-room bypass?
Show answer and explanation
Answer: Require a signed admission token validated on every purchase path.
Question: How do you sell a whole group atomically?
Show answer and explanation
Answer: Use a single authoritative transaction where possible and define all-or-none behavior.
Question: How do you test correctness?
Show answer and explanation
Answer: Race concurrent holds and assert unique sale ownership under retries and expiry.
Operate and verify the design
Hold conflict rate, admission wait, expired-payment cases and stock reconciliation.
Race two buyers for the final seat while delaying the payment callback past hold expiry.
A second scenario to test transfer
Alice holds seat A7 until 12:05. Her payment response is delayed. At 12:06, Bob acquires a new hold after expiry. Alice’s success callback arrives at 12:07. The callback must not mark A7 sold to Alice merely because it says payment succeeded. It must compare the original hold identity. The safe result is a reconciliation/compensation path for Alice, while Bob’s hold remains intact.
A successful payment webhook contains an old hold token. What must the confirmation handler do?
Show answer and explanation
Answer: Authenticate and deduplicate the webhook, compare the current hold and payment state, and refuse to transfer inventory from a newer owner. Route the old payment into void/refund/reconciliation as appropriate; do not blindly mark sold.
Compare alternatives
| Mechanism | Role | Limitation |
|---|---|---|
| Waiting room | Bound load and define fairness | Does not reserve inventory |
| Atomic hold | Protect seat ownership | Conflicts remain normal |
| Hold expiry | Release abandoned inventory | Late external effects need handling |
| Reconciliation | Repair payment mismatch | Requires audit and operations |
Read the diagram
A seat moves available to held to sold. Expired holds return to available; a late successful payment requires reconciliation, not stealing a reallocated seat.
A design-changing exercise
A payment succeeds one second after a seat hold expires. Should it always confirm?
Show answer and explanation
Answer: No. Confirm only the still-valid matching hold under the business policy. If ownership moved, preserve the new owner and reconcile the payment.
Design workshop: atomic multi-seat checkout
Choose reserved seating as the core scope: browse an event, hold adjacent seats, pay and obtain tickets. General admission is an extension with a conditional quantity counter instead of per-seat rows. Assume a five-minute hold, a two-second hold API target and zero double-sold seats. Browsing may be stale; the hold decision is authoritative.
The hold operation records customer/request fingerprint and a hold ID before returning. An identical retry returns the original seat set and expiry; changing seats under the same key returns 409. Read availability is not a reservation. Lock selected seats in event/seat order, check all of them, then update all or none.
BEGIN;
SELECT seat_id,state,hold_id,expires_at
FROM seats WHERE event_id=:event AND seat_id=ANY(:seat_ids)
ORDER BY seat_id FOR UPDATE;
-- Verify exactly the requested count and every row is eligible.
-- Expired holds are eligible only under the chosen ownership rule.
UPDATE seats SET state='held',hold_id=:hold,expires_at=:deadline
WHERE event_id=:event AND seat_id=ANY(:seat_ids);
INSERT INTO holds VALUES (:hold,:customer,:request_hash,:deadline,'active');
-- Persist request result and payment outbox atomically.
COMMIT;Add a unique request map, an expiry index on held seats/holds, and payment_events(provider,event_id) for callback deduplication. The payment worker runs outside this transaction. A successful callback confirms only rows still owned by this hold and a valid confirmation policy. If payment confirmation is allowed after the deadline, define an explicit grace state that prevents reassignment; otherwise refund/void late success. Do not casually mix those policies.
Read the diagram
Hold A7 and A8 under request k1; Hold A8 and A9 under request k2; Lock A7,A8; commit both for hold H1; A8 owned by H1; roll back entire H2; Pay with operation linked to H1; Confirm only matching active hold H1
The waiting room issues a signed token containing event, customer, admission ID and expiry. A server-side admission ledger enforces one use and records the resulting hold request; the signature alone cannot stop replay. FIFO order is a scenario choice, with randomized admission before opening only if explicitly disclosed. Admit based on measured hold capacity and recent contention, not just queue length. Client refreshes show queue progress without repeatedly querying the seat table.
Read the diagram
Concern / Reserved seat / General admission extension; Ownership / Seat row with current hold ID / Atomic quantity reservation; Multi-item request / Lock seats in stable order; all or none / Check available >= requested quantity; Expiry / Release only matching active hold / Subtract held quantity once; Late payment / Never transfer another hold's seat / Never consume newly reassigned stock
At 1,670 admitted requests/s buying four seats, expect roughly 6,680 seat-row updates/s plus requests and payment records. A hot seat's serial critical section, not total database IOPS, bounds contention. If a hold transaction spends 10 ms on the locked authority, a single contested seat can process at most roughly 100 serial decisions/s before waiting. Shed or queue requests rather than open unlimited database connections.
Exercise: A holds two seats, its payment succeeds after expiry, and B now holds one of them. Can A receive the other seat?
Show answer and explanation
Answer: No under an all-or-none two-seat purchase contract. Mark the old payment for compensation and preserve B's ownership. Partial fulfillment is a different product contract requiring explicit customer consent and accounting.
Requirement trace and verification
| Requirement | Implementing design block | Verification link |
|---|---|---|
| R1 — Complete the primary user journey within the stated scope. | P01–P04; P09–P10. | V1 — Exercise the successful journey and the excluded/boundary case stated in the scope. |
| R2 — Preserve the problem's authoritative data invariant under concurrency. | P05–P06; P11–P12; DD1. | V2 — Run the race/replay case below and assert the invariant at the durable authority. |
| R3 — Meet the declared scale and API behavior without unbounded work. | P05–P08; P13–P14; relevant overload deep dives. | V3 — Exercise the API errors/retry identity and recompute the workload assumption that drives partition or capacity. |
| R4 — Recover from the named failure without losing or duplicating the user-visible outcome. | P15–P16; deep dives; Operations contract. | V4 — Race two buyers, delay payment past expiry, replay callbacks, and lose the hold cache; a seat has at most one confirmed booking. |
Operational contract
| Area | Decision and signals |
|---|---|
| SLIs | Hold conflicts; admission wait; payment-unknown age; seat reconciliation lag; duplicate confirmation rate. |
| Overload limits | Bound waiting-room admission, per-seat command queue, active holds, and provider concurrency. |
| Recovery | Reconcile ambiguous payment before release; verify seat authority from durable hold/version state after cache loss. |
| Access controls | Bind holds to authenticated owner and event; protect payment tokens and avoid public seat-owner data. |
| Dominant cost | Ticket inventory reads during sale bursts and payment-provider fees. |
Senior-level interviewer follow-ups
Question: Why not let every admitted buyer lock a seat row immediately?
Show answer and explanation
Answer: That creates lock storms and lets automation occupy scarce inventory. Admission control should smooth the burst; a short transaction then claims a specific seat or seat group atomically.
Question: The payment provider times out after a seat hold expires. Can another buyer take the seat?
Show answer and explanation
Answer: Only after the payment outcome is reconciled under the sale policy. Fence the reservation generation and handle an unknown provider result explicitly; releasing on timeout alone can create a charge without a ticket.
A 60-second design answer
I would put a fair, bounded admission layer in front of the sale, then claim seats with one authoritative conditional transaction and a short lease. Payment has an idempotent intent and reconciliation path, while seat release is generation-fenced. I would monitor queue fairness, hold expiry, lock wait, payment uncertainty, and oversell invariants. The trade-off is throughput versus a strict single-owner inventory decision.
Related concepts to revisit
Technical references
Provider idempotency contract example.
PostgreSQL transaction isolation and concurrent updates.
Show the overlapping multi-seat race
A requests {A7,A8}; B requests {A8,A9}. The authority acquires/updates under a deterministic seat order and commits one all-or-nothing hold. If A wins H1, B cannot keep A9 as a partial successful hold unless the product explicitly permits it. Browsed or streamed availability is a hint, not the transaction result.
H1 owns A7,A8 at hold version 4
B requests A8,A9 -> conflict; neither belongs to B
H1 expires; H2 later owns A8 at version 5
late H1 confirmation -> cannot modify H2Read the diagram
H1 owns A7,A8 at hold version 4; B requests A8,A9 -> conflict; neither belongs to B; H1 expires; H2 later owns A8 at version 5; late H1 confirmation -> cannot modify H2
A waiting-room token controls admission under a bounded, fair policy; it does not grant seat ownership. Payment P1 is linked to H1. If P1 succeeds after H1 is no longer valid, reconcile/compensate under the declared business policy, never steal H2. Expiry updates compare the matching hold/version and remain idempotent. Avoid keeping a database lock open while a user completes payment.
Show answer and explanation
Answer: A TTL lock cannot make the payment provider's result atomic with the seat record. Hold authority, provider identity and admission capacity are distinct boundaries in the diagram.
Decision review: Ticketmaster
Ticketmaster: What it is
A ticket design lets a buyer hold and purchase a declared set of seats without overlapping ownership.
Ticketmaster: Why it is needed
Two requests can overlap in only one seat; independent per-seat success is not an all-or-nothing multi-seat hold.
Ticketmaster: When to use it
Use an authoritative transaction or equivalent protocol when the product requires an atomic seat set and expiry/checkout ordering.
Ticketmaster: How to use it
Bind a stable hold ID to all requested seats, check availability at one decision boundary, and acquire conflicting resources in a consistent order. Conversion to purchase checks the same hold identity and deadline policy.
Ticketmaster: Compare an alternative
Independent seat holds are simpler only if partial success is acceptable. A queue controls admission; it does not alone enforce the winner transition or solve uncertain payment.
Ticketmaster: Worked decision exercise
The following inputs and policies are original exercise assumptions; they are not measurements or undisclosed company behavior.
A requests seats S1,S2 and B requests S2,S3. Under an atomic set policy, one wins its set and the other gets an explicit conflict without silently retaining a partial seat. A late expiry event must compare the current hold before release.
Read the diagram
A requests S1,S2; B requests S2,S3; Atomic set transition gives one winner; Losing request receives conflict, not hidden partial success; Expiry releases only the still-current hold
What if payment may have succeeded as the hold approaches its deadline?
Show answer and explanation
Answer: Keep payment uncertainty distinct from inventory ownership. Follow the declared grace/reconciliation policy without assigning the same unit twice. If the hold is no longer claimable, track the compensating remedy rather than taking a later buyer’s seat.
Ticketmaster: Check the design
Test overlapping sets, reversed resource order, lost purchase response and stale expiry. Measure lock wait, conflict rate and accepted checkout reconciliation.