285: Case Study: Ticket Booking and Scarce Resource Reservation
Learning outcomes
By the end of this lesson, you should be able to:
- explain and apply an availability read model in a realistic implementation;
- explain and apply a reservation hold in a realistic implementation;
- explain and apply expiry in a realistic implementation;
- explain and apply a payment window in a realistic implementation;
- explain and apply hot-event handling in a realistic implementation.
Prerequisites and retrieval
This lesson assumes the earlier 01–06 foundation and the lessons that came before it in this module. Before you read, retrieve one concrete example from a previous project in which the same concern appeared. The point is not to memorize labels. It is to make a defensible design decision for a large-scale distributed service, with its requirements, traffic, failure modes, cost, and operational constraints stated clearly.
Terminology
- Availability read model: A seat map or search result can be cached and slightly stale. It is a useful hint, not a guarantee, until the user attempts a hold or purchase.
- Reservation hold: Atomically create a short-lived hold for a seat or other scarce resource. The implementation commonly relies on database row or unique constraints, or on a partition owner, and includes an expiration time and an owner token.
- Expiry: A hold must be released after its timeout, including when the client disappears without completing the workflow.
- Payment window: Payment may finish after a hold has expired, or a payment provider's callback may arrive late.
- Hot events: A popular concert can create extreme traffic skew around a very small set of resources.
- Fairness and bots: Rate limits, queue tokens, CAPTCHA or bot detection, purchase limits, and auditability are product and system requirements when scarce resources have high value.
Mental model
Treat Case Study: Ticket Booking and Scarce Resource Reservation as a design problem with observable inputs, outputs, invariants, and failure modes. Booking systems are a direct test of contention: many users may request the same scarce seat at nearly the same time. Availability views can therefore be eventually consistent, but the reservation and commit boundary must serialize conflicting claims. A strong implementation makes its assumptions visible, reduces uncertainty at system boundaries, and leaves enough evidence—tests, types, constraints, metrics, or diagrams—to demonstrate why the design is safe.
A useful sequence for both interviews and production design is:
requirement -> constraints -> model -> implementation -> failure analysis -> verification
Do not move straight from a requirement to a library call. First state what must remain true. Then select the mechanism that enforces that invariant.
Deep dive
1. Availability read model
If every seat-map request had to read the write authority, a popular event could overwhelm the very component responsible for protecting inventory. An availability read model separates the high-volume view from the authoritative reservation path. The map or search response can be cached and slightly stale; it helps a user choose a seat, but it cannot promise that the seat is still free.
The system must recheck availability when the user attempts a hold or purchase. That distinction is the part people most often miss: a seat displayed as available is an observation, while a successful hold is a serialized decision.
Decision rule: Use an availability read model deliberately when it makes the contract or invariant easier to prove. If it merely reduces typing while hiding an assumption about freshness or authority, prefer the more explicit design.
2. Reservation hold
The user needs time to complete checkout, but the service cannot let two users believe they own the same seat. A reservation hold atomically creates a short-lived claim on the resource. It usually records an expiration time and an owner token, and its uniqueness is enforced by a database row or unique constraint or by a partition owner that serializes writes for that resource.
The hold is not the final purchase. It is the temporary state between “the user selected this seat” and “the system committed the order.” Retries must be safe, and a request that arrives concurrently with another request must either win the atomic operation or receive a clear conflict rather than observing an ambiguous success.
Decision rule: Use a reservation hold deliberately when it makes the contract or invariant easier to prove. If it merely reduces typing while hiding an assumption about ownership, atomicity, or retries, prefer the more explicit design.
3. Expiry
A client can close its browser, lose connectivity, or abandon payment. Without expiry, those abandoned holds permanently reduce inventory. Holds must therefore become invalid after a timeout even if no client sends a release request.
Use a durable expiry index or queue to drive cleanup, but also validate the expiration time when reading or mutating the hold. Cleanup is asynchronous and can be delayed; delayed cleanup must not accidentally extend a hold's validity. When a cleanup worker races with a payment or retry, the operation must check the current owner token and state before changing it.
Decision rule: Use expiry deliberately when it makes the contract or invariant easier to prove. If it merely reduces typing while hiding an assumption about time, delayed work, or state transitions, prefer the more explicit design.
4. Payment window
Payment introduces a second system with its own timing and failure modes. A provider may authorize payment after the hold has expired, or its callback may arrive later than expected. The service needs an explicit policy for that race: extend the hold under defined conditions, confirm only while the hold is still valid, or compensate and refund a late success.
The payment callback should be idempotent and should refer to the intended order and hold, not simply to a user. A successful payment is not automatically permission to allocate an already released seat. The state transition must apply the reservation policy and leave an auditable result when payment and inventory disagree.
Decision rule: Use a payment window deliberately when it makes the contract or invariant easier to prove. If it merely reduces typing while hiding an assumption about provider callbacks, hold validity, or compensation, prefer the more explicit design.
5. Hot events
Popular concerts create extreme skew: a huge number of requests may target a tiny set of event and seat partitions. Sending all of those requests directly to the write authority turns contention into a capacity problem. Admission queues, waiting rooms, per-event partitions, and aggressive read caching reduce pressure before requests reach the component that owns inventory.
These controls also make load more predictable. They do not replace the atomic hold; they protect it. Fairness and bot controls belong in the admission and purchase policy as well, because a technically correct reservation path can still produce an unacceptable outcome if one actor can monopolize access.
Decision rule: Use hot-event handling deliberately when it makes the contract or invariant easier to prove. If it merely reduces typing while hiding an assumption about skew, admission, or partition ownership, prefer the more explicit design.
6. Fairness and bots
Scarce, high-value inventory attracts automation. Rate limits, queue tokens, CAPTCHA or bot detection, purchase limits, and auditability are not optional decorations around the booking system; they are part of the product and system requirements. A queue token should be checked by the server, and purchase limits must be enforced against trusted account or order data rather than only in the client.
Fairness is also a policy question. Decide what the queue orders, how retries preserve a user's place, how suspicious activity is handled, and what evidence operators can inspect afterward. These choices affect throughput, user experience, and the ability to investigate abuse.
Decision rule: Use fairness and bot controls deliberately when they make the contract or invariant easier to prove. If they merely reduce typing while hiding an assumption about identity, admission, or auditability, prefer the more explicit design.
Worked example
Consider a large-scale distributed service whose requirements, traffic, failure modes, cost, and operational constraints are explicit rather than implied. Start by writing the requirement in one sentence. Then list the input and output contracts and identify which concept above owns each failure mode. The useful separation is straightforward: parsing and validation belong at the boundary; domain rules belong in the domain or service layer; persistence rules belong in the database or repository; presentation rules belong in the client. Combining these concerns can make a happy-path demo shorter, but it makes concurrency and edge cases much harder to reason about.
Client
|
DNS -> CDN / Edge
|
Load Balancer -> API instances -> Cache
| |
+------> Primary datastore
|
+------> Queue / Stream -> Workers
Walk through at least four cases: the normal booking path; an empty or missing value; a duplicate, retry, or concurrent request where relevant; and a dependency failure. For each case, state which layer detects the problem, whether state changes, and what the caller observes. That level of ownership and failure analysis is what a senior code review or technical interview should make visible.
Production perspective
Production correctness means more than “the code works on my machine.” Ask how the design behaves during deployments, retries, partial failures, stale clients, concurrent requests, malformed data, schema changes, and high-cardinality workloads. Prefer explicit contracts, bounded resource usage, structured errors, and measurable behavior. Optimize only after evidence identifies the bottleneck or risk.
When an external dependency is involved, define its timeout and cancellation strategy. When persistence is involved, define transaction and consistency expectations. When state is visible to users, define loading, empty, error, stale, and success states. When security is involved, assume the client can be modified and all network input is untrusted. In this case, also make time and identity observable: hold creation and expiry, payment-provider events, retries, queue admission, and the final inventory decision should be traceable.
Guided lab
Design concert seat booking with a waiting room, a cached seat map, atomic holds, expiry, payment, confirmation, hot-event partitioning, fairness and bot controls, and failure and retry behavior.
Complete the lab with this discipline:
- Write the requirement and two non-requirements.
- List input, output, and error contracts before implementation.
- Implement the smallest correct vertical slice.
- Add at least one invalid-input test and one edge-case test.
- Instrument or inspect the behavior instead of guessing.
- Refactor one hidden assumption into an explicit type, constraint, function, or configuration.
- Explain one alternative design and why you did not choose it.
- Record a short “what would break at 10× scale?” note.
Edge cases and failure modes
- Availability read model: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes. In particular, verify that a stale “available” result is rejected or rechecked correctly at the hold boundary.
- Reservation hold: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes. Verify that only one concurrent claimant wins and that a retry does not create a second hold.
- Expiry: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes. Include delayed cleanup and a cleanup operation racing with payment or a client retry.
- Payment window: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes. Include a late provider callback, duplicate callbacks, and the policy for payment after hold expiry.
- Hot events: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes. Include skewed traffic, queue retries, partition pressure, and requests that bypass or lack valid admission.
Common mistakes and debugging
- Solving the example instead of the requirement: a copied pattern can be syntactically correct but architecturally wrong.
- Hiding uncertainty with assertions, broad exception handlers, permissive schemas, or “temporary”
anyvalues. - Testing only the happy path and discovering the actual contracts only after integration.
- Optimizing before measuring, or selecting a scalable mechanism without a scale requirement.
- Letting client-side behavior stand in for server-side authorization, validation, or persistence guarantees.
For debugging, reproduce the smallest failing case and inspect the actual value or execution plan. Trace the boundary where the invariant first becomes false: the client or cache for stale availability, the API and transaction for the hold, the expiry index and worker for cleanup, the provider callback for payment, or the queue and partition for a hot event. Check logs, state transitions, tokens, timestamps, and metrics at that boundary. Fix the layer that owns the invariant instead of adding a downstream patch.
Interview questions
- What problem does an availability read model solve, and what trade-off or failure mode would make you choose a different approach?
- What problem does a reservation hold solve, and what trade-off or failure mode would make you choose a different approach?
- What problem does expiry solve, and what trade-off or failure mode would make you choose a different approach?
- What problem does a payment window solve, and what trade-off or failure mode would make you choose a different approach?
- What problem does hot-event handling solve, and what trade-off or failure mode would make you choose a different approach?
Checkpoint
Without notes, explain Case Study: Ticket Booking and Scarce Resource Reservation to another developer in five minutes. Your explanation must include one invariant, one edge case, one production failure mode, and one alternative design. Then implement a small example without copying the lesson code.
Mastery checklist
- I can define the core terms precisely.
- I can choose a design from requirements instead of from habit.
- I can implement and test the normal path and edge cases.
- I can explain the runtime, storage, or complexity cost.
- I can identify which layer owns validation, errors, and recovery.
- I can compare at least two reasonable alternatives.
- I can explain how the design changes at larger scale or stricter reliability.
