•CASE STUDY

Fast Host Listings Metrics Page (Airbnb)

4 min read·772 words·Intermediate

Asked at

1 candidate report in Dec 2025

How to use this case study

SDE-2 / Mid

  • Explain pre-aggregating daily metrics per listing
  • Answering a date-range query by summing daily rows

SDE-3 / Senior

  • Go deeper on the storage layout for range queries
  • Batch vs streaming updates
  • One batched API call for many listings
  • Caching

Staff / Principal

  • Discuss hosts with thousands of listings
  • Freshness guarantees
  • Backfills and consistency with booking data

Problem RestatementProblem

Airbnb asked: design the backend for a host's listings page. A host opens a page showing their listings, selects a date range, and for each listing sees aggregated metrics for that range, like views, booking requests, bookings, nights booked, occupancy rate and revenue. The page must load fast, even for professional hosts with hundreds or thousands of listings.

RequirementsRequirements

  • Metrics per listing for any date range (up to ~1–2 years back), plus totals across all listings.
  • Page load under ~500 ms, with pagination and sorting by metric.
  • Data freshness: views can lag a little (minutes to an hour), and bookings and revenue should be accurate.

1.1 Scale

  • 7M listings, 4M hosts. Raw events: billions of views per day.

Deep Dive — Metrics for an arbitrary date rangeDeep dive

A host picks "last 90 days" and expects views, bookings, nights and occupancy for every one of their listings, immediately.

Weak

Aggregate raw events per page load

Scan the event tables filtered by listing and date, and aggregate on the fly.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  PAGE["Host opens the page"] --> SCAN["Scan billions of view and booking events"]
  SCAN --> FILTER["Filter by listing_id and date range"]
  FILTER --> AGG["Group and aggregate"]
  AGG --> SLOW["Seconds to minutes, for every host, on every page load"]

The work is proportional to the raw event volume for a page that shows a few dozen numbers. It also repeats identically every time the page is opened, because yesterday's events cannot change.

Good

Pre-aggregate by day

Keep one summary row per listing per day:

listing_daily_metrics(listing_id, date, views, booking_requests, bookings,
                      nights_booked, available_nights, revenue_cents)
PRIMARY KEY (listing_id, date)

A 90-day range is now 90 small rows, read contiguously because the primary key orders by (listing_id, date). This is the core of the answer and it is usually enough.

The limits show at the edges: "last 2 years" across 40 listings is 29,000 rows per page load, and — the bug that actually bites — occupancy cannot be summed.

Best

Daily plus monthly rollups, with ratios computed after summing

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  EV["Raw events"] --> D["listing_daily_metrics - one row per listing per day"]
  D --> M["listing_monthly_metrics - one row per listing per month"]
  Q["Range query"] --> SPLIT["Whole months from monthly, edges from daily"]
  M --> SPLIT
  D --> SPLIT
  SPLIT --> SUM["Sum numerators and denominators separately"]
  SUM --> RATIO["occupancy = sum(nights_booked) / sum(available_nights)"]
  RATIO --> WRONG["NOT the average of daily occupancy rates"]
  • Two-year ranges become two dozen monthly rows plus a few daily rows at each end, instead of 730 per listing.
  • Store numerators and denominators, never the ratio. Occupancy for a range is sum(nights_booked) / sum(available_nights). Averaging daily occupancy percentages weights a day with one available night the same as a day with fifty, and the number quietly disagrees with what hosts compute by hand — which is the version they will complain about.
  • The same rule applies to every derived metric on the page: conversion, revenue per night, and any other rate.

Rebuild the day's row from raw events when late data arrives, and rebuild the month from its days. Because the rollups are derived rather than incremented, a correction anywhere replays cleanly instead of needing a compensating adjustment.

ArchitectureArchitecture

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    EV["View events"] --> K[("Kafka")]
    BK["Bookings DB"] -->|"CDC"| K
    K --> STR["Stream job - today's counters"]
    K --> LAKE[("Data lake")]
    LAKE --> BATCH["Daily batch - exact daily rows + monthly rollups"]
    BATCH --> MS[("Metrics store - by (listing, date)")]
    STR --> MS
    UI["Host page"] --> API["Metrics API - batch per page"]
    API --> HL[("Host → listings index")]
    API --> MS
    API --> C[("Cache")]
  • Batch job (daily): computes exact daily metrics from bookings (source of truth) and deduplicated views, and writes daily and monthly rows.
  • Streaming job: keeps today's numbers fresh (views, new bookings) until the batch finalizes them.
  • Metrics store: a DB good at range scans by key (Cassandra/HBase, or Postgres partitioned by listing, or an OLAP store like Druid/ClickHouse).

Serving the Page

  1. Get the host's listing IDs (a host → listings index), paginated (e.g., 50 per page).
  2. One batched query for all 50 listings and the date range (not 50 separate calls). Each listing's rows are contiguous, so this is 50 short range scans done in parallel.
  3. Sum per listing, compute ratios, and return rows plus the page total.
  4. Sorting by a metric across 1,000 listings (e.g., "sort by revenue this month"): compute summaries for all the host's listings for that range (1,000 × ~30 rows is fine), sort, and cache the result for the session.
  5. Cache results keyed by (host, range, page) for a few minutes. Common ranges (last 30 days, this month) can be precomputed per host overnight.

Correctness

  • Bookings, cancellations and revenue come from the bookings DB via CDC, so they match what the host sees elsewhere. Cancellations subtract from the day they affect.
  • Time zones: aggregate by the listing's local date.
  • Backfills: if logic changes, recompute daily rows from the data lake and overwrite them (idempotent by primary key).

Wrap-UpWrap-up

Pre-aggregate metrics into daily (and monthly) rows keyed by (listing_id, date), built exactly by a daily batch job from bookings and deduplicated views, with a streaming job keeping today fresh. Serve the page by fetching the host's listings page by page and running one batched range query, summing rows and computing ratios after summing, and cache common ranges and sorted results so even hosts with thousands of listings get a fast page.

More Case Studies

Frequently Asked Questions

What is the Fast Host Listings Metrics Page (Airbnb) system design question?

Fast Host Listings Metrics Page (Airbnb) is a system design interview question asked at FAANG companies. It covers analytics, databases, caching, api design and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Fast Host Listings Metrics Page (Airbnb) question?

Airbnb have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Fast Host Listings Metrics Page (Airbnb) interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Fast Host Listings Metrics Page (Airbnb) question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →