Property Booking Platform
A hard Data Modeling interview practice problem on DataDriven. Design the schema on the canvas: tables, keys and relationships, then get feedback on it.
- Domain
- Data Modeling
- Difficulty
- hard
- Seniority
- L6
Problem
We're building the analytics warehouse for a property booking marketplace. Hosts list properties, guests book stays, and we need to support revenue reporting, host performance, and guest satisfaction analysis. Design the data model.
Five-star listing. Three-star reality.
Worked solution and explanation
Why this problem exists in real interviews
Hospitality models are a Type 2 SCD probe hidden inside a booking question. Property attributes like price, amenities, and host ownership change over time, and analysts must be able to reconstruct what a listing looked like at the moment the booking was made. The signal is whether the candidate reaches for an effective-dated dimension rather than trusting point-in-time joins on natural keys.
Break down the requirements
Step 1: Declare the booking grain
fact_bookings is one row per confirmed reservation. Check-in and check-out are separate date_sk FKs so occupancy and nights can be computed either way.
Step 2: Type 2 the property dimension
dim_properties carries valid_from, valid_to, and is_current. A rate change, amenity change, or host transfer creates a new row with a new property_sk, preserving the old version for historical bookings.
Step 3: Version-lock the fact
fact_bookings.property_sk points at the version of the property that was live at booking time. This is what lets "revenue per 4-bedroom listing" stay stable even after 4-bedrooms get reclassified.
Step 4: Separate reviews from bookings
fact_reviews is its own fact with a one-to-one (or one-to-zero) relationship to bookings. Reviews arrive late, have their own grain, and their own ingestion SLA.
Step 5: Add a conformed geography dimension
Lat/lon and city descriptors live in dim_geography. Multiple facts (bookings, reviews, search impressions) share it so "supply in Barcelona" has one definition.
The solution
Below is one conceptually sound design. The Type 2 dim_properties is the anchor; it guarantees that historical bookings report the attributes the guest actually saw.
- dim_guests: guest_sk BIGINT PK, guest_id TEXT, signup_date DATE, country TEXT
- dim_hosts: host_sk BIGINT PK, host_id TEXT, superhost_status BOOLEAN, onboarded_at TIMESTAMP
- dim_properties: property_sk BIGINT PK, property_id TEXT, host_sk BIGINT FK, property_type TEXT, valid_from TIMESTAMP, valid_to TIMESTAMP, is_current BOOLEAN
- dim_dates: date_sk INT PK, calendar_date DATE, season TEXT
- dim_geography: geography_sk BIGINT PK, city TEXT, country TEXT, lat DECIMAL, lon DECIMAL
- fact_bookings: booking_id BIGINT PK, guest_sk BIGINT FK, property_sk BIGINT FK, checkin_date_sk INT FK, checkout_date_sk INT FK, geography_sk BIGINT FK, nights INT, total_amount DECIMAL
- fact_reviews: review_id BIGINT PK, booking_id BIGINT FK, guest_sk BIGINT FK, property_sk BIGINT FK, rating INT, submitted_at TIMESTAMP
- dim_hosts references dim_properties
- dim_guests references fact_bookings
- dim_properties references fact_bookings
- dim_dates references fact_bookings
- dim_geography references fact_bookings
- fact_bookings references fact_reviews
- dim_guests references fact_reviews
- dim_properties references fact_reviews
The analysis pattern
SELECT
p.property_id,
p.property_type,
SUM(b.total_amount) AS historical_revenue,
COUNT(*) AS bookings,
AVG(r.rating) AS avg_rating,
BOOL_OR(p.is_current) AS includes_current
FROM fact_bookings b
JOIN dim_properties p ON p.property_sk = b.property_sk
LEFT JOIN fact_reviews r ON r.booking_id = b.booking_id
JOIN dim_dates d ON d.date_sk = b.checkin_date_sk
WHERE d.calendar_date >= '2025-01-01'
GROUP BY p.property_id, p.property_type
ORDER BY historical_revenue DESCTrade-offs and alternatives
- Type 2 SCD on properties
- Historical accuracy, clean surrogate joins, versioned reviews. Cost: more dimension rows, ETL complexity around change detection, separate logic to find the current version.
- Type 1 with a snapshot fact
- Simpler dimension, always-current attributes. Cost: historical bookings silently report today's attributes, and any audit of past revenue loses fidelity unless a parallel snapshot table is maintained.
Common follow-up questions
- A host transfers a property to a new host. Is that a Type 2 change, a new property, or something else? (Tests judgment on what counts as a versionable change versus entity replacement.)
- Reviews can be edited up to 48 hours after submission. How do you model the edit history? (Tests whether the candidate appends versions to `fact_reviews` or introduces an audit log.)
- The platform expands to 30 countries, each with its own currency. Where does currency conversion live? (Tests whether the candidate stores native amount plus an FX lookup rather than a single USD column.)
- GDPR deletion of a guest arrives. What rows go, what rows stay, and how are facts anonymized? (Tests hard-delete strategy on `dim_guests` with tombstoning on `fact_bookings`.)
- Property volume grows to 50M versions. How does the Type 2 strategy scale? (Tests partitioning by `valid_from` and indexing `is_current` for the hot-path join.)