Ticket booking architecture.
A named seat can be sold exactly once, and ten thousand people want it at the same instant. Everything here follows from that.
The components
Every row below is read from the graph that produced the diagram above, so the two cannot disagree.
| Component | Tier | Why it is there |
|---|---|---|
| Web & mobile | Client | Supporting component |
| Waiting room | Edge | Admission control before the sale opens |
| API gateway | Edge | Supporting component |
| Seat inventory | Application | The contended resource, one writer per seat |
| Hold service | Application | TTL lock, released automatically |
| Booking service | Application | Supporting component |
| Payments | Application | Supporting component |
| Expiry worker | Application | Returns abandoned holds to inventory |
| Redis | Data | Holds, with TTL doing the cleanup |
| Postgres | Data | Unique constraint is the real guarantee |
| Kafka | Data | Supporting component |
| Payment provider | External | Supporting component |
Design decisions worth arguing about
A diagram shows what was chosen. It does not show what it cost, and that is usually the part that matters in a review or an interview.
A waiting room, because admission control beats scaling
For a sale where demand exceeds supply by orders of magnitude, no amount of capacity makes everyone succeed; it just lets everyone fail more expensively. A waiting room admits users at a rate the system can actually serve, which keeps the experience predictable and stops the backend collapsing. The cost is a queue people can see, which is a product decision as much as a technical one.
Hold with a TTL, so abandonment self-heals
A seat has to be reserved while payment is attempted or two people will pay for it. Holding it forever means abandoned checkouts permanently remove inventory. A TTL makes expiry automatic rather than dependent on a cleanup process running correctly. The tradeoff is choosing the duration: too short and slow payers lose seats they were paying for, too long and inventory looks sold out when it is not.
The database constraint is the real guarantee
Redis holds are a coordination mechanism, not a correctness one: a network partition or an expiry at the wrong moment can let two bookings through. A unique constraint on the seat in the transactional store is what makes double booking impossible rather than unlikely. It means the second booking fails at commit time, late in the flow, which is worse for that user and correct for the system.
Payment cannot happen inside the lock
Payment involves a third party and takes seconds, so holding a database lock across it serialises the whole sale on external latency. The hold exists precisely so that the lock can be released while payment proceeds. The consequence is a window where the seat is neither free nor sold, and every part of the system has to represent that state honestly rather than rounding it to one or the other.
How it changes with scale
Load is not steady, it is a spike measured in seconds at a known time. Autoscaling cannot react fast enough, so capacity has to be provisioned ahead of the on-sale and the waiting room has to absorb the rest. This makes the system unusual: it is sized for a handful of moments per year rather than for its average.
Where it breaks first
Contention on a popular seat or section. Everyone attempts the same rows, so the same keys are contended, and lock waits stack up until requests time out. Timed-out requests are retried by users, which adds load, and the section that everyone wants becomes the section nobody can buy. Randomising suggested seats within a price band spreads contention and is far cheaper than making the locking faster.
Draw this yourself
Open the Diagram tab and describe the system. The agent emits a semantic graph rather than coordinates, so you can edit the components and the layout re-solves instead of drifting.
When the shape is right, Implement in code turns the canvas into a markdown specification, every component, every relationship and the notes, and starts a real turn in the Code tab with it.
Questions about this design
More templates
- URL Shortener System DesignThe canonical read-heavy system: roughly a hundred reads for every write, and a redirect that has to be fast enough that nobody notices it happened.
- Chat Application System DesignLong-lived connections change everything: the hard part is not storing messages, it is knowing which of your servers is holding the socket you need to write to.
- Payment System DesignThe only system on this list where being approximately right is indistinguishable from being wrong, and where the provider, not you, holds the truth.
- RAG Pipeline ArchitectureAlmost every RAG system that disappoints is failing at retrieval, not generation, and the architecture is what decides whether you can tell.