Building a flight booking system
Implement cached itinerary search, fare re-validation at booking, and the hold-pay-confirm reservation with PNR issuance.
Itinerary search over cached inventory
Search assembles connecting itineraries from cached availability, falling back to the GDS on a miss:
def search(origin, dest, date, pax):
key = (origin, dest, date, pax)
flights = fare_cache.get(key)
if flights is None: # miss → query GDS (slow, rate-limited)
flights = gds.query(origin, dest, date, pax)
fare_cache.set(key, flights, ttl="5m") # short TTL — fares change fast
itineraries = build_itineraries(flights) # direct + connections (graph search)
return rank(itineraries, by=["price", "duration", "stops"])
build_itineraries does a constrained graph search over flights/airports (valid connection
times, max stops). Pre-cache popular routes off-peak so common searches never hit the GDS.
Re-validate at booking (freshness)
Cached results can be stale, so confirm the exact fare + availability before holding:
def begin_booking(itinerary, pax):
live = gds.price_and_availability(itinerary, pax) # authoritative, right now
if live.price != itinerary.price or not live.available:
raise FareChanged(live) # show updated price to the user
return live
Hold → pay → confirm
Reserve seats with a temporary hold, take payment, then confirm — releasing on failure (Airbnb/e-commerce pattern, against the airline’s inventory):
def book(itinerary, pax, payment_token, idempotency_key):
live = begin_booking(itinerary, pax)
hold = gds.hold_seats(itinerary, pax, ttl="10m") # temporary reservation (lease)
try:
charge(payment_token, live.price, idempotency_key) # idempotent (payment lesson)
pnr = gds.confirm(hold) # commit the booking → PNR
bookings.save(pnr, itinerary, pax, status="ticketed")
return pnr
except (PaymentError, ConfirmError):
gds.release(hold) # free seats on any failure
raise
The hold prevents the seat being sold to someone else during checkout; the airline’s inventory (or your mirror with a per-seat unique constraint) is the no-double-sell authority.
PNR and post-booking
A confirmed booking is a durable PNR record. Cancellations/changes are a saga with the airline (request refund + release seats), idempotent and reconciled:
def cancel(pnr_id, idempotency_key):
Saga(pnr_id, [
Step("airline_cancel", lambda: gds.cancel(pnr_id), undo=noop),
Step("refund", lambda: refund(pnr_id, idempotency_key), undo=noop),
]).run()
bookings.set(pnr_id, status="cancelled")
Scale and failure handling
- Search load → cache + pre-cached hot routes + parallel GDS fan-out; the GDS is the rate-limited, costly dependency to protect.
- Stale fare at booking → re-validate; surface the new price (don’t silently charge a different amount).
- Payment fails / hold expires → release seats (no orphaned holds — TTL).
- GDS timeout on confirm → status-check + reconcile (don’t double-book or lose the ticket); idempotent confirm by booking ref.
- CP booking → never confirm without a live hold + successful payment.
The takeaway
Concrete signals: cached itinerary search (short TTLs, pre-cache hot routes) over the GDS, fare re-validation at booking, and a hold → pay → confirm reservation with idempotent payment and a PNR, cancellations via a saga. Read-heavy volatile search split from a CP hold-and-confirm booking is the reservation blueprint — and Ticketmaster pushes the contention to the extreme next.