Skip to content

45. Booking and reservations

The archetype. An app in which a user searches scarce, dated inventory, holds one item for a few minutes, pays for it, and afterwards carries a ticket that must work at the door.

A booking app makes one promise. The seat, room or slot you were shown is yours once you pay, and nobody else gets it.

Each clause of that promise costs something.

  • You were shown. Availability and price are read from a source that changes every second, often one that another company runs. What the screen shows is already old.
  • Yours once you pay. Paying takes minutes and passes through a bank. The item must be kept for you during that time, and only for that time.
  • Nobody else gets it. Each item exists once. Two buyers can tap the same seat in the same second, and exactly one of them may win.
  • Afterwards. The purchase produces a ticket that is used days later, at a gate with no signal, and that may change before then.

A shop that sells one unit too many apologizes and refunds. A booking app that sells one seat twice has two people standing at the same seat.

Four concerns dominate the design.

ConcernWhy it dominatesChapter
Inventory holds and consistencyThe hold is the mechanism that keeps the promise, and only the backend can grant oneChapter 21
PaymentsThe payment is tied to a hold that has a deadlineChapter 26
OfflineBooking is Level 0 by necessity, and the ticket must work with no networkChapter 12
Real-time deliveryAvailability, the place in a queue and later changes to the booking all start on the backendChapter 14

The archetype has four variants. They differ in the form of the inventory, the length of the hold and the nature of the ticket.

VariantInventoryWho owns itTypical holdTicket
TravelSeats and rooms, often counted per fare or room type, sometimes namedUsually a supplier outside the app’s backendMinutes, sometimes with a price guaranteeA boarding document or a booking reference
Events with reserved seatingNamed seats on a seat mapThe app’s backend or a ticketing partnerA few minutes, with a countdownA scannable code, often protected against copying
RestaurantsTables per time slot, as counted capacityThe venue, through the app’s backendShort or none. Often no paymentA confirmation and a name at the door
AppointmentsTime slots of one person or one resourceThe provider’s calendarShort or noneA confirmation and a reminder

Two properties in this table drive most of the design. Named items make contention visible. Inventory owned by a supplier makes the app’s backend a client of someone else’s system, with that system’s delays.

These are requirements as a user experiences them. None of them names a design.

What the app does

  • Search by date, place and party size, and show what is available with prices.
  • Show a seat map, a room list or a slot list in which taken items are marked.
  • Let the user select an item and keep it while they enter details and pay.
  • Show how long the selection is kept.
  • Take payment, and state clearly whether the booking is confirmed.
  • For a sale in high demand, let users wait their turn in an orderly way.
  • Show the ticket, and offer to add it to the device wallet.
  • Tell the user about a change: a new time, a new gate, a cancellation.
  • Show the same bookings on every device the user signs in on.

Qualities it must have

  • An item is never sold to two people.
  • A user who pays gets the item, or gets the money back and is told so. There is no third outcome.
  • The price charged is the price on the last screen before payment.
  • The time remaining is truthful.
  • The ticket opens at once, with no network, on a phone that has not opened the app for a week.
  • A change to the booking reaches the user before they act on old information.
  • Under a rush of demand, the app stays orderly. Waiting is acceptable. Errors and lost places in line are not.

Of the seven constraints of Chapter 2, three bind hardest. Untrusted client means the client can never grant itself an item or extend its own deadline. Unreliable network collides with a flow that has a deadline. Process death is likely in the middle of payment, when a bank challenge sends the user to another app.

Run the general probes first. The eight booking probes then examine the hold and the ticket. Chapter 4 describes how to run a probe cleanly.

No probe in this chapter needs a booking or a payment. Stop before you confirm a payment, and leave the flow so that anything you held is released.

Run P12.1 and P12.2 from Chapter 12 twice: once on the search and selection flow, and once on the list of bookings. The two halves of this app sit at opposite ends of the offline scale.

ProbeOn the booking flowOn existing bookings
P12.1 Offline readVisited results may appear, marked as possibly out of date, or nothing appearsBookings and tickets appear, including ones not opened in this session
P12.2 Offline writeThe control is disabled or an error appearsChanges and cancellations are refused
Offline level0 Online only1 Cached reads, with the ticket stored ahead of need

Then run P21.1 from Chapter 21 on selecting, paying, changing and canceling. Typical is refusal for every one of them. The backend decides.

Run P14.1 and P14.2 from Chapter 14 on a seat map or a slot list, with a second device as the source of changes. P45.2 does this as part of its steps. Typical for events with reserved seating is a change within a few seconds, which points to a held connection or a fast poll for that screen. Typical for travel, restaurants and appointments is no live delivery at all: availability is fetched when the screen loads.

P14.4 and P14.5 apply when the provider changes a booking you hold. You cannot trigger that. When such a notification arrives in normal use, turn on airplane mode before opening the app and see whether the booking already shows the change.

Run P28.1, P28.2 and P28.6 from Chapter 28 on the search screen. Typical is backend search that runs on submit, with filters whose counts change as you combine them. A travel search that fills in over several seconds, in groups, is the backend asking several suppliers and merging their answers.

Run P26.1 to P26.3 from Chapter 26, and stop when the payment surface appears. Typical is the external payment provider route, a payment surface with embedded fields or a device wallet sheet, and a pending or expired entry left behind by the abandoned attempt. If that entry shows a deadline, you have seen the hold recorded as data.

Run P8.3 and P8.10 from Chapter 8 on the booking flow, between selection and payment. Typical is a flow that can be found again after process death, and progress that is visible on a second device, since the hold lives on the backend. P45.1 is the same test with the countdown as the measuring instrument.

P45.1 to P45.3 examine the hold. P45.4 and P45.5 examine what the search screen can promise. P45.6 to P45.8 examine the ticket and need a booking that you already hold for your own reasons. Skip them if you have none.

Open worksheet
Do
  1. Select a seat, room or slot and advance until the app shows a countdown or says the selection is being held.
  2. Note the remaining time and the time on your watch.
  3. Force-quit the app and wait one full minute.
  4. Open the app and return to the booking in progress, through whatever route the app offers.
  5. Compare the remaining time with your note, then cancel or back out of the flow.
Watch

Whether the booking in progress can be found again, and whether the countdown lost exactly the time that passed.

Caution. This probe holds real inventory for a few minutes. Choose a date and an offering with plenty of availability, never a sale that is close to selling out. Do not pay. Leave the flow when you are done, so that the hold is released.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Choose an offering in which single items can be told apart, such as a seat on a seat map or one time slot.
  2. On device B, signed in to a second account of your own or not signed in, open the same seat map or slot list and leave it on screen.
  3. On device A, select one item and advance until it is held.
  4. Watch B for thirty seconds without touching it. If nothing changes, reload the screen on B.
  5. If B still shows the item as free, tap it once on B and read the response. Then back out on B.
Watch

Whether B shows the item as taken, when, and what B is told if it tries to select it.

Caution. This probe holds real inventory. Use an offering with plenty of availability, do not pay, and do not select the same item on the second device beyond a single tap.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. With the item held on A and shown as taken on B, note the remaining time on A's countdown.
  2. On A, cancel the booking in progress or back out of the flow to the search screen.
  3. Reload the screen on B every twenty seconds.
  4. Note how long the item takes to become free on B.
  5. If you ended A by force-quit and not by backing out, record that with the result.
Watch

Whether the item becomes free soon after A leaves, or only when the countdown would have ended.

Caution. Run this directly after P45.2, with the same held item. Do not pay.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Online, run a search and open one result, so that both screens have been visited.
  2. Go back to the search screen.
  3. Turn on airplane mode.
  4. Note whether the result list is still shown, and whether it is marked as possibly out of date.
  5. Open the result again and try to select a seat, room or slot.
Watch

How far the flow goes without a network, and at which step it refuses.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Run a search for a date and an offering with a price that is known to move, such as travel a few weeks ahead.
  2. Note the price of one result. Leave the result list on screen and do not refresh it.
  3. Return after thirty minutes with the app still on that list.
  4. Select the result you noted and advance to the summary before payment.
  5. Compare the price with your note, then leave the flow.
Watch

Whether the app lets you continue from the old list, and whether the summary price matches the price you noted.

Caution. This probe opens the summary before payment and may start a hold. Do not pay, and leave the flow when you are done.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Sign in on a device where you have never opened this booking's ticket. A second device is best.
  2. Open the list of bookings so that it loads, and do not open the booking itself.
  3. Turn on airplane mode and force-quit the app.
  4. Open the app, open the booking, and open its ticket or code.
  5. Turn the network back on, open the ticket once, and repeat the offline steps.
Watch

Whether the ticket and its scannable code appear offline before you have ever opened it on this device, and after.

Caution. Use a booking you already hold for your own reasons. Do not make a booking for this probe. If you have none, skip P45.6 to P45.8.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Open the ticket's scannable code online and watch it for three minutes.
  2. Note whether the pattern changes, and whether a timer or an animation accompanies it.
  3. Turn on airplane mode and watch for another three minutes.
  4. Note whether the code is still shown, and whether it still changes.
  5. Take a screenshot if the app allows it, and note whether it is refused or comes out blank.
Watch

Whether the code is fixed or changes, and whether a changing code keeps changing without a network.

Caution. Use a booking you already hold. Do not post or share a picture of the code.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Sign in to your account on a second device.
  2. Open the list of bookings and note whether the booking is there without any action.
  3. If the booking was made as a guest or on a website, look for a way to add it by its reference.
  4. If the app offers a note, a seat preference or a saved traveler that costs nothing to change, change one on the first device.
  5. Watch the second device for the change without touching it, then reopen the booking there.
Watch

Whether the booking follows the account, and how a change made on one device reaches the other.

Caution. Use a booking you already hold. Change nothing about the booking itself.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Each subsection states the design this archetype usually takes and the reason. The components are those of the universal app model in Chapter 3.

A hold is a short reservation of one item for one buyer, with a deadline, recorded on the backend. It exists because selection and payment are separated by minutes, and the item must not be sold to anyone else in between.

An item moves through three states. It is free, held or booked. A hold returns to free in three ways: the buyer leaves and the client releases it, the deadline passes, or the payment fails. A hold becomes a booking in one way: the backend confirms the payment while the hold is still valid.

The hold has three properties that a teardown can test.

  • The backend grants it. The client asks, and the backend answers yes or no. A client that marks a seat as “mine” before the answer arrives is showing an optimistic update on the one action where that is unsafe.
  • The backend owns the deadline. The response carries the deadline, and the client draws the countdown from it. Chapter 8 gives the test: a countdown that is still correct after a force-quit comes from a backend deadline. P45.1 runs it.
  • It ends without the client’s help. A buyer whose phone dies cannot release anything. The deadline is what frees the item. An explicit release when the buyer backs out is a courtesy that returns the item sooner. P45.3 shows whether the app sends one.
function drawCountdown(hold):
// Remaining time is computed from the backend's deadline on every tick.
// Use the offset between backend time and device time, measured from the
// hold response, so that a wrong device clock does not change the result.
remaining = hold.deadline - (deviceNow() + hold.clockOffset)
if remaining <= 0:
showHoldEnded() // the backend has already freed the item
else:
show(remaining)

The length of the hold is a product decision. A long hold lets a slow buyer finish and keeps items away from buyers who would pay now. A short hold frees items quickly and fails buyers whose bank is slow.

When the hold starts is a second decision. A hold at selection gives the buyer certainty early and lets idle visitors lock inventory. A hold at the start of payment protects inventory and lets a buyer fill in a long form for an item that is then gone. P45.2 shows which the app chose.

Chapter 12 places every feature at an offline level. The booking flow is at Level 0, and this is a requirement and not a shortcut.

A queued mutation works when the backend will almost certainly accept it later. A message will be accepted. A request for the last seat will not be, if anyone else asked first. An offline client that showed “seat selected, will confirm when online” would be promising something only the backend can grant. The honest design refuses, and says why.

The same reasoning applies to reads. Cached availability is not availability. Many apps keep the last search results on screen offline and block selection. Some refuse to show them at all. P45.4 separates these.

The ticket is the opposite case. It is a fact that has already been decided, it does not change often, and the moment it is needed is the moment the network is worst. So one app holds two features at two levels: Level 0 before payment and stored data after.

Double-booking prevention as a backend rule

Section titled “Double-booking prevention as a backend rule”

Preventing a double booking is a consistency rule that lives in the backend store. No client design contributes to it, and the client’s checks are only for fast feedback.

The rule is a conditional write. The backend changes an item from free to held only if it is free at the instant of the write, and the check and the write are one atomic step.

function requestHold(itemId, buyer, key):
if holds.exists(key): return holds.get(key) // a retry finds the first attempt
transaction(inventoryStore):
item = read(itemId)
if item.state != Free and not deadlinePassed(item):
return Taken // the other buyer was first
item.state = Held
item.holder = buyer
item.deadline = backendNow() + holdLength
write(item)
return Hold(itemId, item.deadline)

Two requests for the same seat reach this function in some order. The first finds the seat free and takes it. The second finds it held and is told so. Nothing in the result depends on what either client believed.

The form of the inventory changes the write and not the principle.

  • Named items, such as seats, take one conditional write per item.
  • Counted capacity, such as rooms of one type or tables in a time slot, takes a conditional decrement: reduce the count only if it is above zero.
  • Time slots of one resource are named items whose name is a start time.

From outside you observe the rule only through its refusal. P45.2 produces one safely when the second device taps a held item. The message “this seat was just taken” is the Taken branch.

When a supplier owns the inventory, the app’s backend cannot run this write itself. It asks the supplier to hold, and the supplier’s answer is the truth. This adds a slow, fallible step to selection, which a travel app shows as a pause after you choose.

Availability and prices that change between search and checkout

Section titled “Availability and prices that change between search and checkout”

Search must be fast and broad. The inventory source is slow and exact. So search reads from a cache of availability and prices, and the booking flow confirms against the source when the user selects.

StepWhat it readsHow freshBinding
Search resultsA cache of availability and pricesSeconds to minutes old, sometimes moreNo
Detail or seat mapThe source, or a fresher cacheSeconds oldNo
SelectionThe source, through the hold requestCurrentThe item is, for the length of the hold
Summary before paymentThe holdCurrentThe item and the price are

Three client behaviors follow from this table, and P45.5 separates them.

  • Results with a time limit. The client or the backend treats a result list as valid for a stated period and reloads it afterwards. A booking never starts from an old price.
  • A recheck with a notice. Selection returns the current price. If it differs from the price in the list, the client says so and asks the user to accept it.
  • A silent recheck. The summary shows the current price with no comment.

In events with fixed prices the same table describes availability alone. The seat map is the cache. On a busy map staleness is costly, so these apps deliver changes to open maps through a persistent connection or a fast poll, as Chapter 14 describes. The live map reduces refusals. It does not prevent double booking. The conditional write does that.

When a sale opens for a very popular event, far more buyers arrive in the first second than the inventory system can serve. Without control, most see errors, and those who get through are chosen by luck.

A waiting room is a queue in front of the booking flow. It admits buyers at the rate the inventory system can handle and holds the rest on a light screen that costs the backend almost nothing.

  • Before the sale opens, arrivals are collected. At opening they are placed in an order, often a random one, so that arriving early by a second carries no advantage.
  • Each buyer in line holds a queue token. The backend stores the position against the token or the account. The client polls slowly, or holds a connection, to learn its position.
  • When a buyer’s turn comes, the backend issues an admission that is valid for a limited period. Only then can the client load the seat map and request a hold.

The position must survive a lost connection and a restart of the app, so it lives on the backend. The admission must not be forgeable or reusable, so the backend checks it on every booking request.

You cannot trigger a waiting room, and joining a real sale you do not intend to buy from takes a place from someone who does. Record it as passive observation if you meet one in normal use. Note whether a position is shown and whether it survives closing the app.

The payment follows the state machine of Chapter 26: created, awaiting the bank, then confirmed or failed, with the provider callback as the truth. What this archetype adds is a second clock. The hold has a deadline, and the payment has no regard for it.

The backend creates the payment against the hold and stores the link. When the provider callback reports that the bank agreed, the backend converts the hold into a booking, in one transaction that checks the hold is still valid.

function onPaymentConfirmed(payment):
hold = holds.get(payment.holdId)
transaction(inventoryStore):
if hold.state == Held and hold.holder == payment.buyer:
hold.item.state = Booked // valid hold, even if the deadline just passed
booking = bookings.create(hold, payment)
else:
booking = None // the item was released, perhaps resold
match booking:
None: provider.refund(payment); notifyBuyer(PaidTooLate)
otherwise: issueTicket(booking); notifyBuyer(Confirmed)

The hard case is a hold that expires in the middle of a payment. The buyer submitted card details with thirty seconds left, the bank demanded a bank challenge, and the confirmation arrived after the deadline. Three designs handle it.

DesignWhat happensCost
Extend the hold when payment startsThe backend moves the deadline once, by the longest time a payment may takeItems stay locked longer when payments are abandoned
Honor a late confirmation if the item is still freeThe conversion succeeds unless someone else took the itemWorks most of the time, with a rare refund
Refund any late confirmationThe buyer is told the booking failed and the money returnsSimple and unkind. The buyer paid and has nothing

The signature of the first is a countdown that disappears, pauses or jumps when the payment surface opens. Whatever the design, one outcome is forbidden: money taken and no booking, with nothing said.

A robust client stores the hold and the payment reference before it shows the payment surface. After a bank challenge, or after process death, it asks the backend for the state of the booking and shows one of three screens: confirmed, still confirming, or failed with a refund on its way.

Once the booking is confirmed, the backend issues a ticket, and the design problem turns around. The ticket is needed at a gate, in a crowd, often underground or abroad. It must open from the local store with no request.

A ticket takes one or more of these forms.

FormWhere it livesWorks offlineProtection against copying
A static code in the appLocal storeYes, if stored ahead of needNone. A screenshot is as good as the ticket
A rotating code derived on the deviceA secret in secure hardware-backed storage, plus the timeYesA screenshot stops working within a short period
A rotating code from the backendFetched for each viewNoStrong, at the price of needing a network at the gate
A pass in the device walletThe operating system’s walletYesDepends on the pass

Two details decide whether the offline ticket works in practice.

The first is when the ticket reaches the device. A ticket cached by the visit exists only if the user happened to open it while online. A ticket stored ahead of need is written to the local store when the booking is confirmed, when the booking list syncs, or when a push hint arrives. P45.6 separates the two with a device that has never opened the ticket.

The second is the session. A ticket that is in the local store but hidden behind a sign-in screen, because the access credential expired and cannot be renewed offline, is not an offline ticket. Chapter 20 covers what a client may show when it cannot renew.

A pass in the device wallet moves the ticket outside the app. The operating system stores it and displays it without launching the app. Chapter 25 covers surfaces of this kind. The wallet has its own update channel: the backend can push a new version of the pass, so a gate change appears on the pass even if the app is never opened.

The rotating code is the untrusted-client constraint again. A static code can be copied and sold twice. A code that changes with time, and that the scanner checks against the same secret, makes a copy worthless after a short while. P45.7 shows the rotation. It cannot show how the scanner checks it.

Changes and cancellations pushed to the device

Section titled “Changes and cancellations pushed to the device”

A stored ticket is a copy, and copies go stale. A departure moves, a gate changes, an event is postponed, an appointment is canceled by the provider. The user acts on a stale ticket in the physical world.

The design is the receive path of Chapter 14, with the background case as the important one.

  • The backend learns of the change, often from a supplier, and updates the booking.
  • It sends a platform push. The text tells the user. Where the design allows, the push is also a push hint that wakes the client to pull the booking into the local store.
  • It pushes a new version of any pass in the device wallet.
  • The client refetches bookings whenever it comes to the foreground, since a push can be lost.

Chapter 23 covers what a woken client may do. The typical design also schedules local notifications as reminders, which fire with no network.

Changes and cancellations that the user starts are mutations at Level 0, for the reason given earlier in this section. A change releases one item and takes another, at a new price, and only the backend can do either.

Bookings are few, small and owned by the backend. Syncing them is simple by the standards of Chapter 12. The client fetches the full list on sign-in and on each return to the foreground, stores it, and stores each ticket with it. No delta and no conflict handling is needed, because the device never edits a booking offline.

The complication is identity. A booking may have been made as a guest, by another person, or through an agent. Such a booking belongs to a reference and a name, and not to the account. Many apps therefore offer to add a booking by its reference. P45.8 shows whether bookings follow the account, the reference or the device.

Figure 45.1 shows the components that the probes imply for an app whose backend owns the inventory, with a waiting room in front of the booking flow.

Figure 45.1. The client and backend of a booking app. The device wallet sits on the device and is drawn in gray because the operating system runs it.

In the terms of the universal app model, the client has no sync engine and no outbox. The backend has two read paths of different freshness, the availability cache for search and the inventory store for the hold. Where a supplier owns the inventory, the conditional write happens in the supplier’s system and the booking service waits for it.

Figure 45.2 follows the most important flow, from selection to a stored ticket.

Figure 45.2. Hold, pay and confirm. The hold is granted by a conditional write and converted only after the provider callback.

Two failure branches are not drawn. If the item is not free, the first response is a refusal and the client returns to the seat map. If the conversion finds the hold gone, the backend refunds the payment and tells the buyer.

The variant is usually plain from the product. What differs inside a variant is the form of the inventory, the placing of the hold and the nature of the ticket, and each has a discriminating probe. Chapter 5 describes how a signal becomes a claim.

QuestionDiscriminating probeReading
Named items or counted capacityPassive observationA seat map or a named slot means named items. “2 rooms left” or a plain yes or no means a count
Is anything held before paymentP45.1 and P45.2A countdown, and a second device that is refused
Who owns the deadlineP45.1A countdown that is correct after a force-quit
Is availability delivered liveP45.2, then P14.2A change on an open screen with no gesture
How the ticket gets to the deviceP45.6Whether a ticket never opened appears offline
Is the code protected against copyingP45.7Whether the code changes, and whether it does so offline

Travel against events. Travel shows the supplier’s delays: a search that fills in gradually, a pause at selection, and a price that moves between list and summary. Holds are often invisible, because the supplier grants them and the app shows no countdown. Events show contention instead: a live seat map, a prominent countdown, a waiting room, and a ticket with a rotating code.

Restaurants and appointments. Many take no payment, so the hold and the booking collapse into one conditional write at confirmation. Run P45.2 on a slot and you may find that nothing is held until the final tap.

Honest limits. Counted capacity hides contention. Your selection reduces a number you cannot see, so P45.2 gives no result. A hold granted by a supplier may have a deadline the app never shows. In both cases record the hold as “no hold shown”, and treat any claim about what happens on the backend as Speculative.

The signal table collects the behaviors from the eight probes and from normal use.

SignalDesign it points toConfidence
A countdown appears as soon as a seat or slot is selectedA hold with a deadline granted by the backend at selectionInferred
The countdown shows the right remaining time after a force-quit and on a second deviceThe countdown is drawn from a backend deadline and not from a timer in memoryInferred
A seat turns gray on an open seat map while someone else is choosingAvailability changes delivered to open screens over a held connection or a fast pollInferred
A seat shown as free is refused when tapped, with a message that it was just takenA conditional write on the backend guards each item and the availability shown is a lagging copyInferred
A held item becomes free again seconds after the other buyer backs outThe client sends an explicit release and the deadline is only the fallbackInferred
The search screen says results are out of date and reloads themSearch results carry a time limit and booking never starts from an expired priceInferred
The price on the summary differs from the price in the result listSearch reads a cached price and selection confirms it against the inventory sourceInferred
Selecting or paying is refused in airplane mode while past bookings still openBooking is Level 0 and the ticket is stored on the deviceInferred
Before a popular sale opens the app shows a position in line or an estimated waitA waiting room that admits buyers to the booking flow at a controlled rateInferred
The place in line is kept after the app is closed and reopenedThe queue position is stored on the backend against the account or a queue tokenInferred
After paying the screen says the booking is being confirmed and then changes by itselfThe backend waits for the provider callback and then converts the holdInferred
A message says the payment arrived too late and will be returnedThe hold ended before the provider callback and the backend refunds instead of confirmingInferred
A ticket never opened on this device shows its code in airplane modeTickets are synced to the local store ahead of needInferred
The code on the ticket changes every few seconds and keeps changing offlineA rotating code derived on the device from a stored secret and the timeInferred
A pass in the device wallet shows a new time or gate without the app being openedThe backend pushes pass updates through the wallet's own update channelInferred
A notification announces a change and the booking already shows it offlineA push hint woke the client and it pulled the booking into its local storeInferred
A booking made on the web appears in the app after sign-in with no actionBookings are stored against the account on the backendInferred
The app says a few are left at this priceA count from the inventory source or a selling message. The two cannot be told apart from outsideSpeculative

Search the signals from every chapter in the signal lookup.

The same at every size

  • The backend grants the hold with a conditional write and owns the deadline.
  • The booking flow is Level 0.
  • The payment is created against the hold, and the provider callback decides.
  • Every confirmed payment ends in a booking or a refund.
  • The ticket is stored on the device.

What changes

  • Admission. A small app lets every request reach the inventory store. A large one puts a waiting room in front of a popular sale, and issues admissions at the rate the store can take.
  • Availability. A small app reads availability from the inventory store on every search. A large one keeps a separate cache for search and accepts that it lags.
  • Suppliers. A small app books its own inventory. A large travel app asks many suppliers per search, each with its own delays and failure modes, and merges the answers.
  • Tickets. A small app issues a static code. Where resale and copying matter, the code rotates and the scanners hold the means to check it without a network.

From outside you see the waiting room, a search that fills in gradually and a code that rotates.

Chapter 6 describes the worksheet and the order in which to fill it in. The fields in this form are specific to booking. The probes of Section 45.3 fill most of them.

Open worksheet

The fields that the Part II chapters add to the worksheet take these typical values for an event app with reserved seating. A value that differs in your teardown is a finding.

FieldTypical valueConfidence
Offline level of the booking flow (Chapter 12)0 Online onlyObserved
Offline level of bookings and tickets (Chapter 12)1 Cached reads, with tickets stored ahead of needInferred
Posture for selection and payment (Chapter 21)The backend decidesInferred
Foreground mechanism on the seat map (Chapter 14)Held connection or pushInferred
Background delivery of booking changes (Chapter 14)Notification and data readyInferred
Payment route (Chapter 26)External payment providerObserved
Payment record created (Chapter 26)Before the payment surface, linked to the holdInferred
Flow state location (Chapter 8)BackendInferred
Backend components impliedWaiting room, search service with an availability cache, booking service, inventory store with holds, async delivery, external payment provider and platform push as third partiesInferred

The question is usually asked as “design a ticket booking app”, “design seat selection for an event”, or “design a hotel booking flow”.

Clarify first. Ask these, in this order.

  1. Named seats or counted capacity. Named seats bring a seat map and visible contention.
  2. Who owns the inventory, the app’s backend or a supplier.
  3. Whether a sale with extreme demand is in scope. That adds the waiting room.
  4. What the ticket is, and whether it must resist copying.

Then state the promise from Section 45.1. It gives you two acceptance tests: no item is sold twice, and every payment ends in a booking or a refund.

Go deep on three concerns.

  • The hold. The three item states, the conditional write, a deadline the backend owns, and a countdown drawn from that deadline. Say that the flow is Level 0 and why queuing a request for scarce inventory is a false promise.
  • Payment against a deadline. The payment created against the hold, the provider callback as the truth, the conversion that checks the hold, and the three designs for a hold that expires in the middle of payment. Draw Figure 45.2.
  • The ticket. Stored ahead of need, readable with no session renewal, updated by a push hint, and available as a pass in the device wallet. Contrast the static code with the rotating code.

Trade-offs an interviewer expects to hear.

  • A hold at selection against a hold at payment: certainty for the buyer against inventory locked by idle visitors.
  • A live seat map against fetch on load: fewer refusals against the cost of delivering every change.
  • Extending the hold during payment against refunding a late confirmation.
  • A static code against a rotating one: offline simplicity against copying.

Say plainly that the client is a view of the backend’s decision until the ticket exists, and a holder of a durable copy afterwards.

  • A booking app promises that the item you were shown is yours once you pay and that nobody else gets it. Inventory holds, payments, consistency and the offline ticket dominate its design.
  • A hold is a short reservation with a deadline. The backend grants it by a conditional write, owns the deadline, and frees the item without the client’s help.
  • Double-booking prevention is a backend rule. The client’s view of availability is a lagging copy, and a live seat map only reduces refusals.
  • The booking flow is Level 0, because a queued request for scarce inventory promises what only the backend can grant. Search prices are estimates until selection confirms them.
  • The payment is created against the hold. The backend converts the hold after the provider callback, and a confirmation that arrives after the hold has gone ends in a refund that the buyer is told about.
  • A waiting room admits buyers to a high-demand sale at a controlled rate and keeps each position on the backend.
  • The ticket is stored on the device ahead of need, as a code or as a pass in the device wallet. A rotating code protects against copying.
  • Changes and cancellations start on the backend and reach the device by platform push and a refetch. Bookings follow the account, or a reference, to every device.