Why your booking engine is slow (and it isn't your hosting).

A walk-through of waterfall API calls, the four-line change that recovered 2.4× conversion for one of our resort clients, and how to measure whether the same pattern is hiding inside your own stack.

Published14 May 2026
Reading time9 minutes
Author
Panagiotis Athanasakopoulos
Founder & Software Engineer
TopicPerformance, hospitality

Last winter we were asked to look at a booking engine that had quietly stopped converting. The team had spent eight months on a redesign — new typography, new photography, a calmer hero, a faster CDN. Lighthouse said it was fine. The marketing director was confused. The CFO was less confused than upset. Could we take a look?

The symptom

On the staging environment, everything felt fine. On production, with real availability data, the page took roughly nine seconds to become useful. The HTML was on screen in 600 milliseconds. The room cards weren't.

The graphs told a story we'd seen before: a near-flat CPU profile, a relaxed memory line, and a network waterfall that looked like a small staircase descending into the floor. The page wasn't slow because the server was slow. It was slow because the page was making one request, waiting for it, then deciding what to ask for next.

Diagram · waterfall vs parallelised request chain
Before / after: nine sequential calls collapsing into two parallel ones.

What was actually slow

The booking flow was orchestrating nine separate API calls — rates, availability, taxes, supplements, restrictions, room features, hero photography, breadcrumb data, and a final "is this still valid?" handshake — and it was doing them in series, because the rates response told it which room IDs to fetch, and the rates response was 2.4 seconds deep.

None of those calls were individually pathological. Each took between 180ms and 900ms. The cumulative latency was nine seconds because of how they were chained. As one of our engineers put it on the call: "this isn't a performance problem, it's a graph problem."

The page wasn't slow because the server was slow. It was slow because the page was making one request, waiting for it, then deciding what to ask for next.

The fix, in four lines

The fix wasn't to make any single call faster. It was to stop making the page wait for them. We did three things:

  • Pushed the dependent decisions up into a single GraphQL resolver — one round-trip from the client, one query plan on the server.
  • Parallelised what the resolver itself fetched (rates and restrictions do not need to be sequential).
  • Pre-warmed the photography URLs in the HTML response so the browser could start downloading them before the JS bundle had finished parsing.

Here's the relevant change to the resolver, edited down to the part that matters:

// Before — sequential, blocks on rates
const rates = await getRates(propertyId, dates);
const rooms = await getRooms(rates.roomIds);
const tax  = await getTax(propertyId, rates.total);

// After — parallel, single round-trip
const [rates, rooms, tax] = await Promise.all([
  getRates(propertyId, dates),
  getRooms(propertyId, dates),  // doesn't need rates.roomIds
  getTax(propertyId, dates),
]);

Four lines, depending on how you count them. The page now becomes useful in about 1.4 seconds on a cold cache. Conversion came back, and the redesign — which was, for the record, very good — finally got the credit it deserved.

How to measure your own

If you suspect the same shape of problem, here are the three things we'd look at first:

  1. Open your network waterfall on a real device, on a real network. Not your laptop on the office Wi-Fi. The staircase pattern is unmistakable.
  2. Count the requests between "HTML arrived" and "first useful paint". If it's more than three, you almost certainly have a graph problem.
  3. Look at server-side timing headers. If your server timing adds up to a fraction of the client-perceived time, the latency is travelling, not computing.

A note on tooling

You don't need exotic observability for this. Chrome DevTools' waterfall, a couple of console.time() calls, and a willingness to refresh the page on your phone will get you 80% of the way there. The remaining 20% is where a tool like SpeedCurve or Sentry's performance tab starts paying for itself.

Takeaway

Most performance problems in modern web apps aren't about how fast the code runs. They're about when it runs, and what it's waiting on. The graphs you're looking at are mostly travel, not work.

We've now seen this same shape of problem in four different booking engines and two e-commerce stacks. The fix is almost always the same: collapse the dependency graph, parallelise the rest, and pre-warm what the browser will obviously ask for next. None of it is novel. All of it is forgettable. That's why it keeps happening.

Have a problem like this?

We probably have
a stronger opinion.

If something in this essay rang a bell — about your own product, your own pipeline, your own night auditor — we'd be happy to take a look. No pitch deck, no funnel. Just a conversation in plain language.

AgencyPlateon 55, 104 35
Athens, Greece
Write to us