Design a Hotel Reservation System (System Design Interview)
How to prevent double booking: inventory per room type per night, conditional updates vs locks vs optimistic concurrency, overbooking, payment holds and…
Designing a hotel reservation system: common questions
How do you prevent double booking?
Store a reserved count per room type per night and increment it with a conditional update (reserved + 1 <= limit) inside one transaction for all nights. Add an idempotency key so retries do not create a second reservation.
Should I use pessimistic or optimistic locking for bookings?
A conditional update is usually best. Optimistic version checks work when collisions are rare but waste retries on hot dates; row locks are correct but queue up and must never be held during payment.
Related chapters
- Start here in System Design
- How a system design interview is graded
- The delivery framework: five phases and a clock
- Numbers to know: what one machine can really do
- System Design Concepts: CAP, SQL vs NoSQL, Message Queues
- SQL vs NoSQL: How to Choose a Database (System Design)
- Traffic Jumps 10×: What Breaks First? (System Design)
- Proxy vs Load Balancer vs API Gateway (System Design)
- CAP Theorem Explained Simply, with Examples
- Why Use a Message Queue? System Design Explained
- Message Queue vs Pub/Sub vs Kafka: Differences
- Polling vs Long Polling vs SSE vs Webhooks (System Design)
- API design in System Design
- REST vs GraphQL vs gRPC: Choosing an API (System Design)
- API Goals Canvas: Design APIs from the Consumer
- REST API Design: Resources, Methods, Status Codes
- HTTP Semantics, Richardson Maturity Model and HATEOAS
- GraphQL Explained: Over-fetching, Schema, N+1 Problem
- RPC and gRPC Explained: Protobuf, HTTP/2, When to Use
- API Design Best Practices: 9 Principles with Examples
- API Payload Design: Names, Types, Links and Errors
- Mapping Database Tables to API Resources, Step by Step
- API Design for Complex Schemas: Trees, Polymorphism, Bookings
- API Pagination, Idempotency Keys, Errors and Versioning
- Network-Efficient APIs: HTTP Caching, ETags, Field Selection
- Async APIs: 202 Accepted, Webhooks and Bulk Operations
- API Versioning and Evolution Without Breaking Clients
- API Contracts: OpenAPI, Contract Testing and Docs
- API Security: Authentication, Authorization, Rate Limiting
- OAuth 2, OpenID Connect and Scopes for APIs
- OWASP API Top 10 Explained: BOLA, SSRF and More
- Running APIs: Gateways, Mesh, Rate Limits, Canaries
- API Troubleshooting Interview: Slow APIs and Unexpected Errors
- API Design Review: A Staff-Level Capstone
- Spotify Web API: Resources, IDs, Payloads and Paging
- Spotify Web API: OAuth PKCE, Scopes, Quotas, Rate Limits
- Spotify Web API Changes: Real API Evolution Lessons
- Database-heavy designs in System Design
- Design a Distributed Key-Value Store (System Design Interview)
- Design a Digital Wallet (System Design Interview)
- Design S3-like Object Storage (System Design Interview)
- Games in System Design
- Shard City: Learn Database Sharding by Playing a Game
- System Design Case Studies: Flash Sale, Chat, ChatGPT
- Flash Sale System Design: Who Gets the Last Item
- Food Delivery App System Design: Which Database?
- Cache Stampede: Why the Database Fell Over and the Fix
- Live Streaming System Design: Scaling for a Final
- ChatGPT System Design: Streaming LLM Responses
- RAG System Design: Product Q&A on Sale Day
- IRCTC Tatkal System Design: Booking Under Extreme Load
- Group Chat System Design: WebSockets and Fan-Out
- Authentication in System Design: Sessions, JWT, OAuth
- Authentication Methods Compared: Sessions, JWT, OAuth
- JWT in System Design: Sessions, Revocation, Refresh Tokens
- JWT Interview Questions and Answers
- Life of a session, step by step
- JWT Structure Explained: Header, Payload, Signature
- Forge lab: why verification must be strict
- JWT vs Session Authentication: Which to Use
- The validation pipeline: where a bad request dies
- How to Revoke a JWT: Blocklists and Short Expiry
- Refresh Tokens and Rotation Explained
- Logout in depth: one device, all devices, SSO
- JWT Security Best Practices
- OAuth 2.0 and OpenID Connect Explained
- Where to Store JWT: localStorage vs HttpOnly Cookie
- JWT in Microservices: API Gateway vs Service Validation
- Microservices, keys, and trust boundaries
- Sender-constrained tokens: making a stolen token useless
- Authentication vs Authorization Middleware
- Failure explorer: what breaks when X fails
- Capacity planner: sessions vs JWT vs hybrid
- Edge cases catalog
- Test it, measure it, migrate to it
- Anti-patterns to reject in a design review
- Design checklist and cheat sheet
Keep learning
Overview
Read, then write Row lock Conditional update Version check Each transaction waits for the previous one to release the row. Correct, but the queue grows on a hot date, and holding the lock while calling a payment API would be a disaster. The database checks and increments atomically. No read-modify-write gap and no lock held across round trips. This is the default answer. Version checks are great when collisions are rare (a quiet hotel). On New Year’s Eve everyone collides, so most of the work is wasted retries. Lab · Deluxe rooms, 31 December: 20 rooms, 16 taken, 10 guests press Book at once Room slots = TOTAL && i confirmations sent reserved counter afterwards wasted retries system design · database-heavy designs · hotel reservation Design a hotel reservation system Traffic is small, so the hard part is correctness: never sell the same room twice, never charge twice for one click, and never leave a room blocked forever by a guest who closed the tab. Almost every decision here is about one row in a database and who gets to change it.
1. Requirements and numbers
Search availability, view a hotel, reserve a room type for a date range, pay, cancel. Hotel staff manage rooms and prices. Book a type, not a room Guests buy “a Deluxe King for 3 nights”. The room number is assigned at check-in. This makes inventory a count, not a calendar per room. Hotels deliberately sell a few % more than they have, because some guests never show. The limit is a per-hotel setting. Strong correctness on booking, high availability on search, a few hundred ms is fine for the booking click. hotels, 1 million rooms bookings a day (70% full, 3-night stays) average bookings, peaks ~50/s more searches than bookings The write rate fits one database easily. The search rate does not need the booking database at all. Keep those two paths apart.
2. API
The Idempotency-Key is generated by the client when the booking form opens. A double click, a refresh or a mobile retry sends the same key, and the server returns the first result instead of booking again.
3. Data model: one inventory row per room type per night
A three-night stay touches three inventory rows. Pre-create rows for the next one to two years with a daily job: 1 million rooms is only ~20,000 room types, so two years is about 15 million small rows.
4. The race: ten guests, four rooms
The naive code reads the count, checks it in the app, then writes. Between the read and the write, other guests do the same thing. Try each fix. For a multi-night stay, run the conditional update for every night inside one transaction and require all of them to change one row. Always update nights in date order, so two overlapping stays lock rows in the same order and cannot deadlock.
5. Holds while the guest pays
Payment takes seconds to minutes and can fail. Do not hold a database lock during it. Reserve first, then pay. Reservation service Payment provider Book (key 9f1c) one transaction create payment intent reservationId as reference webhook: succeeded PENDING → CONFIRMED only if still PENDING A sweeper finds PENDING rows past hold_expires_at, marks them EXPIRED and decrements inventory in the same transaction.
6. Scaling reads, and when to shard
cached availability, may lag seconds Hotel page cache + CDN for photos always checks the primary database Stale data is fine for browsing. The booking click is the only place that must be exact. Cache availability Shard by hotel_id If one primary is not enough (a booking platform with millions of properties), shard by hotel. A reservation and all its inventory rows stay on one shard, so the transaction stays local. Keep inventory and reservations together Splitting them into two services with two databases turns one transaction into a saga with compensations. Only split if you have a strong reason. Hot dates
7. Concurrency options at a glance
No oversell Fast when quiet Fast on a hot date Read then write SELECT … FOR UPDATE Conditional UPDATE Optimistic version Database CHECK (reserved <= limit) A CHECK constraint is a cheap safety net under whichever strategy you choose: even buggy code cannot push reserved past the limit.
8. What to say in the interview
Model inventory as a count per room type per night, not a calendar per physical room. Show the read-then-write race, then fix it with a conditional update in one transaction, nights in date order. Use an idempotency key with a unique constraint against double clicks and retries. Reserve with a hold, pay outside the transaction, confirm conditionally, expire with a sweeper. Shard by hotel_id only if needed, keeping each booking on a single shard. Related: booking site like Airbnb (calendar per listing) · Tatkal at 10:00 · isolation levels
More courses
- Jobs, linked to lessons — Browse jobs where every requirement links to the chapter that teaches it. See how ready you are, how long the gap takes to close, and which listings look stale.
- Match a job post to courses — Paste a job post. Each skill is matched to a track here and put in learning order, foundations first.
- React from open source with patterns — Patterns extracted from Material UI, Radix, React Aria, React Hook Form, Redux Toolkit, TanStack, and Next.js — not slides about hooks.
- Database in depth — How a query becomes work. Indexes, isolation, locks, and the plans that explain why a page is slow — not an ORM tour.
- Build a real API with NestJS — One ticket-booking API, built in order: a plain Node server first, then NestJS modules, validation, a real database, and the features real clients lean on.
- Microservices — Microservice patterns one diagram at a time: how to split a monolith, how services talk, and how data stays consistent without distributed transactions.
- Kafka — A beginner-to-senior path through event logs, reliable delivery, production operations, and disaster recovery.
- AI engineering for software engineers — Eight free reads, in order, for software engineers moving into AI: when to build an agent, how to build one, how to test it, and how the model works underneath.
- Frontend system design — How a UI is sliced when it is a product, not a page: ownership of state, cache, routes, and what happens when the network lies.
- TypeScript in depth — The type system as a design tool. Inference, narrowing, generics, and the errors that mean the model is wrong — not a catalog of utility types.
- CSS in depth — See the box, then the page. Visual chapters from the box model through Flexbox surprises, Grid, selectors, and units, then backgrounds, shapes, and visual effects, each with a playground.
- Accessibility — Foundations first (WCAG in plain words, semantic HTML, lists, alt text), then hands-on HTML labs, a full pass of WAI-ARIA 1.3, and a final fix-the-page challenge.
- Docker — What an image is, why layers cache, and how containers find each other on a network.
- Redis — Fifteen Redis patterns, one picture each: cache, session, lock, counter, rate limiter, queue, and when Redis is the wrong tool.
- Kubernetes — Kubernetes from zero to operators: why it exists, Pods, Deployments and Services, then configuration, networking, storage, scheduling, scaling and security.