Whatever the domain, the vocabulary is identical. Separate the inventory (the resources) from the reservation (a hold on one), and treat concurrency as a first-class concern, not an afterthought. Map the four nouns of your specific problem onto these five and the design writes itself.
📦
Inventory
The finite set of bookable resources — seats, rooms, spots, copies. Each is free or held.
🎫
Reservation
A hold on one resource for a window of time. Has its own lifecycle (held → confirmed → …).
Compute cost on confirm/checkout — flat, per-hour, dynamic. A pluggable Strategy.
🔒
Concurrency
The hard part: two requests must never grab the same resource. Atomic check-and-set.
Generic
Parking Lot
BookMyShow
Library
Hotel
Inventory
parking spots
seats in a show
book copies
rooms
Reservation
ticket
booking
loan
reservation
Availability
free spot by type
free seats
available copy
free room for dates
Pricing
fee by duration
seat-class price
late fine
nightly rate
Time window
entry → exit
show time
borrow → due
check-in → out
02
The reservation lifecycle
A reservation is a little state machine. The crucial state is HELD — a temporary hold that must expire if not confirmed, so abandoned carts don't lock inventory forever. On confirm it becomes a real booking; on timeout or cancel it releases the resource back to the pool.
SEARCHING
→hold
HELD
TTL expiry
→pay
CONFIRMED
→arrive
CHECKED_IN
→done
COMPLETED
→timeout
EXPIRED / CANCELLED
→ release resource
WHY A SEPARATE “HELD” STATE?
Without it you either confirm instantly (no time to pay) or never reserve (everyone races for the same seat at checkout). A short-lived hold with a TTL gives the user time to pay while guaranteeing the resource is returned if they vanish. This is the single most important idea in the family.
03
The core challenge — never double-book
The interview is this problem. Two requests read “seat available”, both write “booked”, and now two people own seat 14F. The fix is to make check-and-set atomic — wrap find-then-hold in a lock (pessimistic) or use a conditional/compare-and-swap write (optimistic). Watch the race with the lock off, then on:
NO LOCK (race condition)
Request A
reads seat → AVAILABLE
1 seat
Request B
reads seat → AVAILABLE
Both read “available” before either writes — the classic lost-update race.
Pessimistic (lock)
Lock the resource (or row), check, set, unlock. Simple and correct; can limit throughput under heavy contention. Good default for an LLD answer.
Fine-grained (atomic claim)
Give each resource its own atomic flag and compareAndSet(free → held); only the winning thread proceeds, the loser scans on. Lets unrelated bookings run in parallel.
04
Design decisions that matter
01
Hold first, confirm later
Never jump straight to BOOKED. Reserve a short-lived HELD with a TTL so the user can pay, and a background sweep (or lazy check) releases expired holds.
02
Make the booking step atomic
The whole find-free → mark-held sequence is one critical section. A lock per resource (or an optimistic conditional write) is what prevents double-booking.
03
Pricing is a Strategy
Flat, per-hour, per-night, surge, seat-class — keep these out of the manager behind a PricingStrategy so new schemes don't touch core logic.
04
Model time explicitly
Reservations occupy a [from, to) interval. Availability = “no overlapping reservation for this resource”. Overlap checks are the heart of hotels and meeting rooms.
05
The design patterns you'll reuse
Reservation lifecycleState
HELD → CONFIRMED → … → COMPLETED / EXPIRED, each permitting different actions.
PricingStrategy
Swap flat / hourly / dynamic pricing without touching the manager.