How to Design Accommodation Platform like Airbnb

How to Design Accommodation Platform like Airbnb — A Senior+ Guide | Ayodhyya

Building search, booking, pricing, and trust systems at 7M+ listing global scale

Senior+ System Design Guide — July 21, 2026 — By Ayodhyya

1. Introduction — Airbnb at Global Scale

Airbnb fundamentally disrupted the hospitality industry by creating a two-sided marketplace connecting hosts who have space with guests who need a place to stay. Since its founding in 2008, the platform has grown to host over 7 million listings across 220+ countries and regions, facilitating hundreds of millions of nights booked annually. The platform processes billions of dollars in transactions each quarter and serves as the backbone for millions of hosts who depend on it for their livelihood.

Designing an accommodation platform at this scale is one of the most fascinating system design challenges because it combines elements of marketplace design, real-time inventory management, geo-spatial search, dynamic pricing, payment processing, trust and safety, and content delivery. Unlike many other marketplace platforms, accommodation booking has unique constraints: inventory is perishable (an unsold night is revenue lost forever), each listing is unique, availability changes in real-time, and pricing is highly dynamic.

In this comprehensive guide, we will walk through the complete system design of an Airbnb-like accommodation platform. We will cover everything from high-level architecture down to database sharding strategies, from the booking state machine to the dynamic pricing engine, from trust and safety mechanisms to multi-region deployment strategies. This guide is designed for senior+ engineers preparing for system design interviews or architects building similar platforms in production.

Key Insight: Airbnb's core technical challenge is maintaining consistency across a highly concurrent, globally distributed system where the same room cannot be double-booked while simultaneously serving millions of search queries per second.

2. Requirements Gathering

Functional Requirements

RequirementDescriptionPriority
Property SearchSearch listings by location, dates, guests, price range, and amenities with geo-filteringP0
Listing ManagementHosts can create, edit, and manage listings with photos, descriptions, pricing, and availabilityP0
BookingGuests can book listings for specific date ranges with availability validationP0
Payment ProcessingSecure payments with split payment between platform and host, holds, and refundsP0
Calendar & AvailabilityReal-time availability calendar with instant book and request-to-book optionsP0
Reviews & RatingsTwo-way review system after completed staysP0
MessagingReal-time messaging between hosts and guestsP1
Dynamic PricingPrice suggestions based on demand, seasonality, events, and competitionP1
Trust & SafetyIdentity verification, fraud detection, and content moderationP0
Map-Based SearchInteractive map view for property discoveryP1
RecommendationsPersonalized property recommendations based on user behaviorP1
Host DashboardAnalytics, earnings reports, booking management for hostsP1
Cancellation PoliciesFlexible, moderate, strict, and custom cancellation policiesP0

Non-Functional Requirements

  • Availability: 99.99% uptime (less than 52 minutes downtime per year)
  • Latency: Search results returned within 200ms (p99), booking confirmation within 500ms
  • Consistency: Strong consistency for booking (no double-booking), eventual consistency for search results
  • Throughput: Handle 500K search queries per second during peak, 10K bookings per second
  • Scalability: Support 100M+ registered users, 7M+ active listings
  • Durability: Zero data loss for booking and payment records
  • Geo-Distribution: Low-latency access from all major markets globally

3. Capacity Estimation

MetricDailyMonthlyAnnual
Total Users150M MAU500M+ registered
Search Queries200M6B72B
Page Views500M15B180B
Bookings1.5M45M540M
Active Listings7M7M+
Reviews500K15M180M
Messages Sent10M300M3.6B
Photos Uploaded2M60M720M
Revenue (GTV)$1.5B$45B$540B

Storage Estimation

Listing data: 7M listings × ~5KB metadata = 35 GB

Booking records: 540M bookings/year × ~2KB = ~1 TB/year

Reviews: 180M reviews/year × ~1KB = 180 GB/year

Photos: 720M photos/year × ~3MB avg = 2.1 PB/year (stored in S3, metadata in DB)

User profiles: 500M users × ~2KB = 1 TB

Bandwidth Estimation

Inbound: 2M photos × 3MB = ~6 TB/day = ~70 MB/s average

Outbound: 500M page views × ~500KB avg page = 250 TB/day = ~2.9 GB/s average

Scale Note: These numbers are approximate based on publicly available data about Airbnb and similar platforms. Actual numbers fluctuate significantly by season, with summer months seeing 2-3x the traffic of winter months.

4. Data Model

The data model for an accommodation platform is inherently complex due to the relationships between users (both hosts and guests), listings, bookings, availability, pricing, and reviews. Below we present the core entities and their relationships.

Entity Relationship Overview

erDiagram USER { uuid id PK string email UK string name string phone string profile_photo_url boolean is_host boolean is_verified jsonb verification_documents timestamp created_at timestamp updated_at } LISTING { uuid id PK uuid host_id FK string title text description string property_type string room_type int max_guests int bedrooms int beds int bathrooms decimal base_price jsonb amenities jsonb house_rules jsonb location boolean is_active boolean instant_book timestamp created_at } LISTING_PHOTO { uuid id PK uuid listing_id FK string url string thumbnail_url int sort_order boolean is_verified timestamp uploaded_at } AVAILABILITY_CALENDAR { uuid id PK uuid listing_id FK date date boolean is_available decimal price int min_nights int max_nights } BOOKING { uuid id PK uuid listing_id FK uuid guest_id FK uuid host_id FK date check_in date check_out int guests string status decimal total_price decimal service_fee decimal host_payout string cancellation_policy jsonb pricing_breakdown timestamp created_at timestamp updated_at } REVIEW { uuid id PK uuid booking_id FK uuid reviewer_id FK uuid listing_id FK int rating text comment jsonb category_ratings timestamp created_at } MESSAGE { uuid id PK uuid conversation_id FK uuid sender_id FK text body boolean is_read timestamp sent_at } CONVERSATION { uuid id PK uuid listing_id FK uuid host_id FK uuid guest_id FK timestamp last_message_at } PAYMENT { uuid id PK uuid booking_id FK uuid payer_id FK decimal amount string currency string status string payment_method jsonb payout_details timestamp created_at } PRICING_RULE { uuid id PK uuid listing_id FK string rule_type jsonb conditions decimal adjustment_percent boolean is_active } USER ||--o{ LISTING : "hosts" USER ||--o{ BOOKING : "books" USER ||--o{ REVIEW : "writes" USER ||--o{ MESSAGE : "sends" LISTING ||--o{ LISTING_PHOTO : "has" LISTING ||--o{ AVAILABILITY_CALENDAR : "has" LISTING ||--o{ BOOKING : "receives" LISTING ||--o{ REVIEW : "receives" LISTING ||--o{ PRICING_RULE : "has" BOOKING ||--o{ REVIEW : "generates" BOOKING ||--o{ PAYMENT : "has" CONVERSATION ||--o{ MESSAGE : "contains"

Core Table Definitions

TableStorage EngineShard KeyIndexEst. Size/Year
usersInnoDB (MySQL)user_idemail, phone1 TB
listingsInnoDB + ESlisting_idhost_id, location (geo)50 GB
availability_calendarInnoDB + Redislisting_idlisting_id + date (unique)200 GB
bookingsInnoDBlisting_idguest_id, check_in, status1 TB
reviewsInnoDB + ESlisting_idbooking_id (unique)180 GB
messagesCassandraconversation_idsender_id, sent_at500 GB
paymentsInnoDBbooking_idpayer_id, status500 GB
pricing_rulesInnoDBlisting_idlisting_id + rule_type10 GB

Key Design Decisions

The availability_calendar table is one of the most critical and heavily accessed tables. For each active listing, we maintain pre-computed availability rows for the next 365 days. This means 7M listings × 365 days = 2.56 billion rows — far too large for a single database. We shard this by listing_id and heavily cache hot listings in Redis.

Bookings use listing_id as the shard key rather than guest_id because the majority of query patterns involve checking bookings for a specific listing (availability check, host dashboard). Guest-centric queries (my trips) are handled through secondary indexes or denormalized views.

Messages use Cassandra instead of MySQL because the access pattern is append-heavy, time-series oriented, and benefits from Cassandra's linear write scalability and tunable consistency.

5. API Design

All APIs follow RESTful conventions with JSON payloads. Authentication uses JWT tokens with refresh token rotation. Rate limiting is applied per-user and per-IP. All endpoints are versioned under /api/v1/.

MethodEndpointDescriptionAuth
GET/api/v1/searchSearch listings by location, dates, guests, filtersOptional
GET/api/v1/listings/{id}Get listing details, photos, calendar, reviewsOptional
POST/api/v1/listingsCreate a new listingHost
PUT/api/v1/listings/{id}Update listing detailsHost
GET/api/v1/listings/{id}/calendarGet availability calendar for a listingOptional
PUT/api/v1/listings/{id}/calendarUpdate availability calendarHost
GET/api/v1/listings/{id}/priceGet price estimate for date rangeOptional
POST/api/v1/bookingsCreate a new bookingGuest
GET/api/v1/bookings/{id}Get booking detailsGuest/Host
POST/api/v1/bookings/{id}/confirmHost confirms a bookingHost
POST/api/v1/bookings/{id}/cancelCancel a bookingGuest/Host
GET/api/v1/users/me/tripsGet current user's bookingsGuest
GET/api/v1/users/me/listingsGet host's listingsHost
POST/api/v1/reviewsSubmit a review for a completed bookingGuest/Host
GET/api/v1/conversationsList user's conversationsAuth
POST/api/v1/conversations/{id}/messagesSend a messageAuth
POST/api/v1/paymentsProcess payment for a bookingGuest
POST/api/v1/listings/{id}/photosUpload listing photosHost
GET/api/v1/map/searchMap-bound search with clusteringOptional
GET/api/v1/recommendationsPersonalized listing recommendationsOptional

Search API Request/Response Example

// Request: GET /api/v1/search?lat=40.7128&lng=-74.0060&checkin=2026-08-01&checkout=2026-08-05&guests=2&price_min=50&price_max=300&room_type=entire_home

// Response:
{
    "search_id": "srch_abc123",
    "results_count": 1847,
    "listings": [
        {
            "id": "lst_xyz789",
            "title": "Cozy Brooklyn Loft with Skyline View",
            "lat": 40.6892,
            "lng": -73.9857,
            "price_per_night": 145.00,
            "total_price": 623.50,
            "rating": 4.87,
            "review_count": 342,
            "room_type": "Entire home/apt",
            "thumbnail_url": "https://cdn.airbnb.com/photos/lst_xyz789_thumb.webp",
            "host": {
                "name": "Sarah",
                "avatar": "https://cdn.airbnb.com/avatars/sarah.webp",
                "is_superhost": true
            },
            "amenities": ["wifi", "kitchen", "washer", "air_conditioning"],
            "instant_book": true,
            "distance_km": 3.2
        }
    ],
    "pagination": {
        "page": 1,
        "per_page": 20,
        "total_pages": 93
    }
}

6. High-Level Architecture

The platform uses a microservices architecture with clear domain boundaries. Each service owns its data and communicates via a combination of synchronous REST/gRPC for real-time queries and asynchronous message queues for event-driven workflows.

graph TB subgraph "Client Layer" WebApp["Web App
(React)"] MobileiOS["iOS App
(Swift)"] MobileAndroid["Android App
(Kotlin)"] end subgraph "Edge Layer" CDN["CDN
(CloudFront)"] WAF["WAF / DDoS
Protection"] APIGW["API Gateway
/ Load Balancer"] end subgraph "Service Layer" SearchSvc["Search Service"] ListingSvc["Listing Service"] BookingSvc["Booking Service"] PricingSvc["Pricing Service"] PaymentSvc["Payment Service"] UserSvc["User Service"] ReviewSvc["Review Service"] MessageSvc["Messaging Service"] TrustSvc["Trust & Safety"] NotifSvc["Notification Service"] RecSvc["Recommendation
Service"] CalendarSvc["Calendar Service"] end subgraph "Data Layer" ES["Elasticsearch
Cluster"] MySQL["MySQL
(Sharded)"] Redis["Redis
Cluster"] Cassandra["Cassandra
Cluster"] S3["S3 / Object
Storage"] GraphDB["Neo4j
(Graph)"] end subgraph "Infrastructure" Kafka["Apache Kafka"] SQS["SQS / RabbitMQ"] Consul["Service
Discovery"] Prometheus["Prometheus
& Grafana"] end WebApp --> CDN MobileiOS --> CDN MobileAndroid --> CDN CDN --> WAF --> APIGW APIGW --> SearchSvc APIGW --> ListingSvc APIGW --> BookingSvc APIGW --> PaymentSvc APIGW --> UserSvc APIGW --> ReviewSvc APIGW --> MessageSvc SearchSvc --> ES SearchSvc --> Redis ListingSvc --> MySQL ListingSvc --> S3 BookingSvc --> MySQL BookingSvc --> Kafka PricingSvc --> Redis PricingSvc --> ES PaymentSvc --> MySQL UserSvc --> MySQL ReviewSvc --> MySQL ReviewSvc --> ES MessageSvc --> Cassandra TrustSvc --> MySQL CalendarSvc --> MySQL CalendarSvc --> Redis RecSvc --> GraphDB RecSvc --> ES BookingSvc --> SQS SQS --> NotifSvc NotifSvc --> Kafka

Service Responsibilities

ServiceResponsibilityDatabaseScaling Strategy
Search ServiceFull-text and geo search, filtering, sorting, availability overlayElasticsearch + RedisHorizontal (stateless)
Listing ServiceCRUD for listings, photo management, content moderationMySQL + S3Read replicas + cache
Booking ServiceBooking creation, state management, conflict resolutionMySQL (sharded)Sharding by listing_id
Calendar ServiceAvailability management, blocked dates, sync across channelsMySQL + RedisSharding by listing_id
Pricing ServiceDynamic pricing calculations, price suggestions, seasonal rulesRedis + MySQLHorizontal + cache
Payment ServicePayment processing, payouts, splits, holds, refundsMySQL (ACID)Read replicas + queue
User ServiceAuth, profiles, verification, preferencesMySQLRead replicas
Review ServiceReviews, ratings aggregation, content moderationMySQL + ESHorizontal + cache
Messaging ServiceReal-time chat, notifications, message historyCassandraHorizontal (partition by conv)
Trust & SafetyFraud detection, identity verification, content scanningMySQL + ML pipelineHorizontal
RecommendationPersonalized listings, similar properties, trendingNeo4j + ESBatch + real-time pipeline

Search is the most complex and highest-traffic subsystem. It must handle hundreds of thousands of queries per second while returning results in under 200ms. The system combines Elasticsearch for full-text and geo queries, Redis for caching hot results, and a real-time availability overlay to filter out booked dates.

Search Architecture

sequenceDiagram participant G as Guest participant GW as API Gateway participant SS as Search Service participant ES as Elasticsearch participant Redis as Redis Cache participant CS as Calendar Service G->>GW: Search(lat, lng, dates, guests, filters) GW->>SS: Forward search request SS->>Redis: Check cache (key = search params hash) alt Cache Hit Redis-->>SS: Cached results else Cache Miss SS->>ES: Geo-bounded search + filters ES-->>SS: Candidate listings (top 200) SS->>CS: Check availability for candidates CS-->>SS: Available listings (top 100) SS->>Redis: Cache results (TTL 30s) end SS->>SS: Apply ranking (price, rating, relevance) SS-->>GW: Ranked results (top 20) GW-->>G: Search response

Elasticsearch Index Structure

{
    "mappings": {
        "properties": {
            "listing_id": { "type": "keyword" },
            "title": { "type": "text", "analyzer": "standard" },
            "description": { "type": "text", "analyzer": "standard" },
            "location": { "type": "geo_point" },
            "property_type": { "type": "keyword" },
            "room_type": { "type": "keyword" },
            "max_guests": { "type": "integer" },
            "bedrooms": { "type": "integer" },
            "beds": { "type": "integer" },
            "bathrooms": { "type": "integer" },
            "base_price": { "type": "scaled_float", "scaling_factor": 100 },
            "rating": { "type": "float" },
            "review_count": { "type": "integer" },
            "amenities": { "type": "keyword" },
            "host_is_superhost": { "type": "boolean" },
            "instant_book": { "type": "boolean" },
            "is_active": { "type": "boolean" },
            "updated_at": { "type": "date" }
        }
    }
}

Search Ranking Algorithm

Search results are ranked using a multi-factor scoring algorithm that balances relevance, quality, and conversion probability:

FactorWeightDescription
Geo Relevance25%Distance from search center point; closer is better with exponential decay
Listing Quality Score20%Composite of rating, review count, response rate, acceptance rate
Conversion Probability20%ML model predicting likelihood of booking based on user profile and listing attributes
Price Competitiveness15%How the listing price compares to similar listings in the same area
Recency10%Newer listings and recently updated listings get a small boost
Superhost Premium5%Superhost listings receive a ranking boost
Sponsored5%Promoted listings appear in designated slots

Search Filters Implementation

The search service supports over 40 filter dimensions. The most commonly used filters include:

  • Location: Geo bounding box or radius from center point, with support for custom drawn areas on the map
  • Dates: Overlaid against the availability calendar to exclude unavailable listings
  • Guests: Filter by max_guests capacity
  • Price Range: Min/max nightly price (pre-computed from pricing engine)
  • Property Type: House, apartment, cabin, villa, treehouse, etc.
  • Room Type: Entire place, private room, shared room
  • Amenities: WiFi, pool, kitchen, air conditioning, hot tub, washer, etc.
  • Booking Options: Instant book, flexible cancellation, self check-in
  • Host Language: Filter by host's spoken languages
  • Accessibility: Step-free guest entrance, wide doorways, etc.
Performance Tip: Hot search queries (e.g., "NYC, 2 guests, next weekend") are cached with a 30-second TTL. For the top 1% of search queries that account for 20% of traffic, we use a pre-computed search index that is refreshed every 5 minutes.

8. Calendar & Availability System

The calendar system is the backbone of the entire platform. Every listing has a daily availability grid that determines whether it can be booked on any given date. The calendar must support:

  • Reading availability millions of times per second across search and listing detail pages
  • Writing availability updates from hosts blocking/unblocking dates
  • Atomic booking operations that claim dates without allowing double-booking
  • Sync with external calendars (iCal, Google Calendar, VRBO) to prevent conflicts

Calendar Data Structure

For each listing, we pre-compute availability rows for 365 days into the future. The key table uses a unique constraint on (listing_id, date) to prevent race conditions at the database level.

ColumnTypeDescription
listing_idUUIDForeign key to the listing
dateDATEThe specific calendar date
is_availableBOOLEANWhether the date is open for booking
priceDECIMALOverride price for this specific date (NULL = use base + rules)
min_nightsINTMinimum stay requirement for this date
max_nightsINTMaximum stay allowed starting from this date
blocked_reasonENUMNULL, HOST_BLOCKED, BOOKED, MAINTENANCE

Availability Check Flow

flowchart TD A[Guest requests date range] --> B{Dates in Redis cache?} B -->|Yes| C{All dates available?} B -->|No| D[Query MySQL: SELECT * FROM availability
WHERE listing_id = ? AND date BETWEEN ? AND ?] D --> C C -->|Yes| E{Instant Book enabled?} C -->|No| F[Return unavailable dates] E -->|Yes| G[Reserve dates with SELECT FOR UPDATE] E -->|No| H[Create pending request
Notify host] G --> I{Lock acquired?} I -->|Yes| J[Hold dates for 30 minutes
Create reservation token] I -->|No| K[Return conflict error
Another booking in progress] J --> L[Guest completes payment] L --> M[Confirm booking] M --> N[Update ES index] N --> O[Send confirmations]

Calendar Sync with External Channels

Many hosts list their properties on multiple platforms. The calendar sync system uses iCal feed parsing and webhook-based push notifications to keep availability consistent across channels. When a booking is made on any platform, the dates are blocked across all connected channels within 60 seconds.

public class CalendarSyncService
{
    private readonly ICalParser _icalParser;
    private readonly ICalendarRepository _calendarRepo;
    private readonly IEventPublisher _eventPublisher;

    public async Task SyncExternalCalendarAsync(
        Guid listingId, string icalUrl, CancellationToken ct)
    {
        var feed = await _icalParser.ParseFromUrlAsync(icalUrl, ct);
        var blockedDates = feed.Events
            .Where(e => e.IsBlocked)
            .Select(e => new CalendarUpdate
            {
                ListingId = listingId,
                Date = e.Date,
                IsAvailable = false,
                BlockedReason = BlockedReason.ExternalSync,
                ExternalSource = feed.Source,
                SyncedAt = DateTime.UtcNow
            })
            .ToList();

        await _calendarRepo.BulkUpsertAsync(blockedDates, ct);
        await _eventPublisher.PublishAsync(new CalendarSyncedEvent
        {
            ListingId = listingId,
            UpdatedDates = blockedDates.Select(d => d.Date).ToList()
        }, ct);
    }
}

9. Booking Flow & State Machine

The booking flow involves multiple services coordinating to ensure a reservation is properly created, payments are processed, and both parties are notified. The booking goes through a well-defined state machine.

Booking State Machine

stateDiagram-v2 [*] --> Pending: Guest initiates booking Pending --> Confirmed: Host accepts / Instant book + payment OK Pending --> Expired: No response within 24h (request-to-book) Pending --> Cancelled: Guest cancels before confirmation Confirmed --> CheckedIn: Guest checks in CheckedIn --> Completed: Stay completes Completed --> Reviewed: Both parties leave reviews Reviewed --> [*] Confirmed --> CancelledByGuest: Guest cancels (policy applies) Confirmed --> CancelledByHost: Host cancels (penalty applies) CancelledByGuest --> Refunding: Refund processing CancelledByHost --> Refunding: Full refund + host penalty Refunding --> Refunded: Refund complete Refunded --> [*] Expired --> [*]

Booking Creation — Detailed Flow

sequenceDiagram participant G as Guest participant BS as Booking Service participant CS as Calendar Service participant PS as Pricing Service participant PayS as Payment Service participant NS as Notification Service participant ES as Elasticsearch G->>BS: Create Booking(listing_id, dates, guests) BS->>PS: Calculate total price PS-->>BS: Price breakdown (nightly × nights + fees + taxes) BS->>CS: Reserve dates (atomic hold) alt Dates available CS-->>BS: Dates reserved (30min hold) BS->>PayS: Process payment(hold) alt Payment successful PayS-->>BS: Payment held BS->>BS: Create booking record (status=PENDING) alt Instant Book BS->>BS: Auto-confirm booking BS->>CS: Confirm dates BS->>PayS: Capture payment BS->>NS: Notify host & guest BS->>ES: Update availability index else Request to Book BS->>NS: Notify host (24h to respond) end BS-->>G: Booking created else Payment failed BS->>CS: Release date hold BS-->>G: Payment error end else Dates unavailable CS-->>BS: Conflict detected BS-->>G: Dates no longer available end

Double-Booking Prevention

Preventing double-bookings is the most critical correctness requirement. We use a SELECT FOR UPDATE pattern with row-level locking on the availability table:

public class BookingService
{
    private readonly AppDbContext _db;
    private readonly ICalendarService _calendar;

    public async Task<BookingResult> CreateBookingAsync(
        CreateBookingRequest request, CancellationToken ct)
    {
        using var transaction = await _db.Database
            .BeginTransactionAsync(IsolationLevel.Serializable, ct);

        try
        {
            // Lock all calendar rows for the date range
            var calendarRows = await _db.AvailabilityCalendar
                .FromSqlRaw(
                    "SELECT * FROM availability_calendar " +
                    "WHERE listing_id = @listingId " +
                    "AND date BETWEEN @checkIn AND @checkOut " +
                    "FOR UPDATE",
                    new SqlParameter("@listingId", request.ListingId),
                    new SqlParameter("@checkIn", request.CheckIn),
                    new SqlParameter("@checkOut", request.CheckOut))
                .ToListAsync(ct);

            // Verify all dates are available
            if (calendarRows.Any(r => !r.IsAvailable))
            {
                await transaction.RollbackAsync(ct);
                return BookingResult.Fail("Some dates are no longer available");
            }

            // Create booking record
            var booking = new Booking
            {
                Id = Guid.NewGuid(),
                ListingId = request.ListingId,
                GuestId = request.GuestId,
                HostId = request.HostId,
                CheckIn = request.CheckIn,
                CheckOut = request.CheckOut,
                Guests = request.Guests,
                Status = BookingStatus.Pending,
                TotalPrice = request.TotalPrice,
                CreatedAt = DateTime.UtcNow
            };

            _db.Bookings.Add(booking);

            // Mark dates as booked
            foreach (var row in calendarRows)
            {
                row.IsAvailable = false;
                row.BlockedReason = BlockedReason.Booked;
            }

            await _db.SaveChangesAsync(ct);
            await transaction.CommitAsync(ct);

            return BookingResult.Success(booking);
        }
        catch
        {
            await transaction.RollbackAsync(ct);
            throw;
        }
    }
}

10. Dynamic Pricing Engine

The pricing engine is one of the most sophisticated components. It uses a combination of base pricing, rule-based adjustments, and ML-based optimization to suggest optimal nightly rates. Airbnb's Smart Pricing adjusts prices dynamically based on over 100 signals.

Pricing Factors

mindmap root((Dynamic Pricing)) Base Price Host-set base rate Property characteristics Location value Supply Factors Listing availability Nearby listing supply Host response rate Demand Factors Search volume for area Booking lead time Day of week patterns Seasonality High season multiplier Holiday surcharges School vacation periods Events Conferences & conventions Sports events Concerts & festivals Competition Similar listing prices Hotel price index Market positioning ML Optimization Conversion rate optimization Revenue maximization A/B test results

Price Calculation Pipeline

StepComponentAdjustmentExample
1Base PriceHost-set nightly rate$150/night
2Day-of-WeekWeekend premium / weekday discount+15% Fri-Sat
3SeasonalitySeasonal multiplier+40% summer in beach cities
4Event SurgeLocal event demand spike+80% during Super Bowl
5Lead TimeLast-minute or far-out discount-20% if booking < 3 days out
6Occupancy RateAdjust based on upcoming occupancy-10% if next 30 days low occupancy
7Competitor AnalysisCompare with similar listingsAlign with market median ±10%
8ML ModelNeural network optimization+$8 predicted optimal point
9Min/Max BoundsEnforce host-defined floors/ceilingsClamp to $100-$300 range
10Round & ValidateRound to nearest $5$213 → $215

Pricing Service Implementation

public class PricingEngine
{
    private readonly IPricingRuleRepository _rules;
    private readonly IEventRepository _events;
    private readonly IMarketDataRepository _marketData;
    private readonly IPricingModel _mlModel;
    private readonly ILogger<PricingEngine> _logger;

    public async Task<PriceQuote> CalculatePriceAsync(
        PricingRequest request, CancellationToken ct)
    {
        var listing = await GetListingAsync(request.ListingId, ct);
        var nightlyPrices = new List<NightlyPrice>();

        for (var date = request.CheckIn; date < request.CheckOut; date = date.AddDays(1))
        {
            var price = await CalculateNightlyPriceAsync(listing, date, ct);
            nightlyPrices.Add(new NightlyPrice
            {
                Date = date,
                BasePrice = listing.BasePrice,
                AdjustedPrice = price,
                Factors = GetAppliedFactors(listing.Id, date)
            });
        }

        var subtotal = nightlyPrices.Sum(p => p.AdjustedPrice);
        var cleaningFee = listing.CleaningFee;
        var serviceFee = Math.Round(subtotal * 0.14m, 2);
        var occupancyTax = Math.Round(subtotal * 0.08m, 2);

        return new PriceQuote
        {
            ListingId = request.ListingId,
            NightlyPrices = nightlyPrices,
            Subtotal = subtotal,
            CleaningFee = cleaningFee,
            ServiceFee = serviceFee,
            OccupancyTax = occupancyTax,
            TotalPrice = subtotal + cleaningFee + serviceFee + occupancyTax,
            Currency = listing.Currency,
            CalculatedAt = DateTime.UtcNow
        };
    }

    private async Task<decimal> CalculateNightlyPriceAsync(
        Listing listing, DateTime date, CancellationToken ct)
    {
        var price = listing.BasePrice;

        // Day of week adjustment
        price *= GetDayOfWeekMultiplier(date);

        // Seasonal adjustment
        price *= await GetSeasonalMultiplierAsync(listing.Location, date, ct);

        // Event surge
        var eventSurge = await _events.GetEventSurgeAsync(listing.Location, date, ct);
        price *= eventSurge;

        // Lead time adjustment
        var leadDays = (date - DateTime.UtcNow.Date).Days;
        price *= GetLeadTimeMultiplier(leadDays);

        // Occupancy-based adjustment
        var upcomingOccupancy = await GetUpcomingOccupancyAsync(listing.Id, ct);
        price *= GetOccupancyMultiplier(upcomingOccupancy);

        // Competitor alignment
        var marketMedian = await _marketData.GetMedianPriceAsync(
            listing.Location, listing.PropertyType, listing.RoomType, ct);
        price = AlignWithMarket(price, marketMedian);

        // ML model adjustment
        var mlAdjustment = await _mlModel.PredictOptimalPriceAsync(
            new PricingFeatures
            {
                ListingId = listing.Id,
                Date = date,
                CurrentPrice = price,
                MarketMedian = marketMedian,
                UpcomingOccupancy = upcomingOccupancy
            }, ct);
        price += mlAdjustment;

        // Enforce bounds
        price = Math.Max(price, listing.MinPrice ?? listing.BasePrice * 0.6m);
        price = Math.Min(price, listing.MaxPrice ?? listing.BasePrice * 3.0m);

        return Math.Round(price / 5) * 5; // Round to nearest $5
    }

    private decimal GetDayOfWeekMultiplier(DateTime date) => date.DayOfWeek switch
    {
        DayOfWeek.Friday => 1.15m,
        DayOfWeek.Saturday => 1.20m,
        DayOfWeek.Sunday => 1.05m,
        _ => 1.0m
    };

    private decimal GetLeadTimeMultiplier(int leadDays) => leadDays switch
    {
        <= 1 => 0.80m,
        <= 3 => 0.90m,
        <= 7 => 0.95m,
        <= 30 => 1.0m,
        <= 90 => 1.05m,
        _ => 1.10m
    };
}

11. Payment Processing

Payment processing for an accommodation platform is uniquely complex because of split payments (guest pays platform, platform pays host), payment holds (capture at check-in), multi-currency support, and refunds with varying policies.

Payment Flow

sequenceDiagram participant G as Guest participant PayS as Payment Service participant PSP as Payment Service Provider
(Stripe/Adyen) participant Ledger as Financial Ledger participant Host as Host Payout G->>PayS: Submit payment for booking PayS->>PSP: Authorize $500 (hold) PSP-->>PayS: Authorization confirmed PayS->>Ledger: Record hold (booking_id, amount) PayS-->>G: Payment authorized Note over PayS: At check-in date... PayS->>PSP: Capture $500 PSP-->>PayS: Capture confirmed PayS->>Ledger: Split payment Note right of Ledger: Guest → Platform: $500\nPlatform → Host: $425 (85%)\nPlatform Fee: $75 (15%) PayS->>Host: Initiate payout ($425) Host-->>PayS: Payout initiated PayS->>Ledger: Record payout

Payment Tables

TablePurposeKey Fields
payment_intentsTracks authorization and capture lifecycleid, booking_id, amount, currency, status, PSP reference, created_at
payoutsTracks host payout batchesid, host_id, amount, currency, status, payout_method, initiated_at
ledger_entriesDouble-entry bookkeeping recordsid, transaction_id, account, debit, credit, balance, created_at
refundsRefund transactions with reason codesid, payment_id, amount, reason, status, initiated_by, created_at
payment_methodsSaved payment methods (tokenized)id, user_id, type, last_four, PSP_token, is_default

Financial Ledger Implementation

The ledger is the single source of truth for all financial records. It uses double-entry bookkeeping where every transaction creates balanced debit and credit entries. This ensures auditability and makes reconciliation straightforward.

public class FinancialLedger
{
    private readonly LedgerDbContext _db;

    public async Task<LedgerEntry[]> RecordBookingPaymentAsync(
        BookingPaymentRecord record, CancellationToken ct)
    {
        using var transaction = await _db.Database
            .BeginTransactionAsync(IsolationLevel.Serializable, ct);

        var entries = new List<LedgerEntry>();

        // Guest payment: credit the platform receivables account
        entries.Add(new LedgerEntry
        {
            Id = Guid.NewGuid(),
            TransactionId = record.BookingId,
            Account = AccountType.PlatformReceivables,
            Debit = record.TotalAmount,
            Credit = 0,
            Currency = record.Currency,
            Description = $"Guest payment for booking {record.BookingId}",
            CreatedAt = DateTime.UtcNow
        });

        // Host payout: debit the platform, credit host account
        entries.Add(new LedgerEntry
        {
            Id = Guid.NewGuid(),
            TransactionId = record.BookingId,
            Account = AccountType.HostPayout,
            Debit = 0,
            Credit = record.HostPayoutAmount,
            Currency = record.Currency,
            Description = $"Host payout for booking {record.BookingId}",
            CreatedAt = DateTime.UtcNow
        });

        // Platform fee: debit receivables, credit revenue
        entries.Add(new LedgerEntry
        {
            Id = Guid.NewGuid(),
            TransactionId = record.BookingId,
            Account = AccountType.PlatformRevenue,
            Debit = 0,
            Credit = record.ServiceFee,
            Currency = record.Currency,
            Description = $"Service fee for booking {record.BookingId}",
            CreatedAt = DateTime.UtcNow
        });

        _db.LedgerEntries.AddRange(entries);
        await _db.SaveChangesAsync(ct);
        await transaction.CommitAsync(ct);

        return entries.ToArray();
    }
}

12. Review & Rating System

The review system uses a two-way blind review model — both guest and host submit reviews simultaneously, and neither can see the other's review until both have submitted (or the 14-day window expires). Reviews are a critical trust signal for the platform.

Review Categories and Ratings

CategoryScaleApplies To
Overall Rating1-5 stars (with 0.5 increments)Guest → Listing
Cleanliness1-5 starsGuest → Listing
Accuracy1-5 starsGuest → Listing
Communication1-5 starsGuest → Host
Location1-5 starsGuest → Listing
Check-in1-5 starsGuest → Listing
Value1-5 starsGuest → Listing
Accuracy of Listing1-5 starsHost → Listing (rare)
Guest Communication1-5 starsHost → Guest
House Rules Compliance1-5 starsHost → Guest
Cleanliness1-5 starsHost → Guest

Review Aggregation

The aggregate rating displayed on a listing uses a weighted Bayesian average rather than a simple arithmetic mean. This prevents listings with very few reviews from appearing at the top with artificially high ratings.

Formula: Rating = (v × R + m × C) / (v + m), where v = number of reviews, R = arithmetic mean, m = minimum reviews threshold (e.g., 10), C = global average rating (e.g., 4.7).

Review Visibility Rules

  • Reviews are published only after both parties submit or the 14-day window expires
  • Reviews can be flagged for content policy violations (hate speech, discrimination, threats)
  • Hosts can publicly respond to reviews but cannot edit or delete them
  • Platform may remove reviews that violate content guidelines but this is rare and audited
  • Reviews older than 3 years are hidden from the default listing view but remain in search ranking calculations

13. Trust & Safety

Trust and safety is arguably the most critical non-functional system. It encompasses identity verification, fraud detection, content moderation, discrimination prevention, and incident response. The T&S system must protect both hosts and guests while maintaining the platform's reputation.

Trust Architecture

graph LR subgraph "Verification Pipeline" ID["Identity
Verification"] Phone["Phone
Verification"] Email["Email
Verification"] Photo["Profile Photo
Verification"] Gov["Government ID
Scan + Liveness"] end subgraph "Detection Systems" Fraud["Fraud Detection
(ML Pipeline)"] Spam["Spam & Scam
Detection"] Disc["Discrimination
Detection"] Content["Content
Moderation"] end subgraph "Response Systems" Auto["Automated
Actions"] Human["Human
Review Queue"] Appeal["Appeals
Process"] Law["Law Enforcement
Request Portal"] end Gov --> Fraud Phone --> Spam Content --> Auto Fraud --> Human Spam --> Human Disc --> Human Human --> Appeal

Identity Verification Levels

LevelRequirementsBadges EarnedTrust Score Impact
Level 1Email + Phone verificationIdentity Verified+10 points
Level 2Government ID scan with liveness detectionID Verified+25 points
Level 3Address verification (utility bill or bank statement)Address Verified+15 points
Level 4Background check (US/selected countries)Background Checked+20 points

Fraud Detection Signals

The fraud detection system uses an ensemble of rule-based filters and ML models that analyze over 200 features in real-time:

  • Behavioral signals: Click patterns, session duration, form fill speed, navigation anomalies
  • Payment signals: Card BIN country mismatch, multiple failed payments, rapid booking attempts
  • Content signals: Listing photo EXIF data, description similarity to known scams, pricing anomalies
  • Network signals: Shared devices, IP addresses, payment methods across accounts
  • Temporal signals: Unusual booking times, rapid succession bookings, last-minute high-value bookings

14. Messaging Between Host & Guest

The messaging system enables real-time communication between hosts and guests before, during, and after a stay. It handles over 10 million messages per day and must support rich media, translations, and quick replies.

Messaging Architecture

graph TB subgraph "Client" App["Mobile/Web App"] end subgraph "Real-time Layer" WS["WebSocket
Gateway"] PubSub["Redis Pub/Sub"] end subgraph "Message Processing" MsgSvc["Message Service"] TransSvc["Translation
Service"] SpamSvc["Spam Filter"] NotifSvc["Notification
Service"] end subgraph "Storage" Cassandra["Cassandra
(Message History)"] S3["S3
(Media Assets)"] end App <-->|WebSocket| WS WS <--> PubSub PubSub --> MsgSvc MsgSvc --> TransSvc MsgSvc --> SpamSvc MsgSvc --> Cassandra MsgSvc --> NotifSvc MsgSvc --> S3

Message Features

  • Real-time delivery: WebSocket-based bidirectional communication with automatic reconnection
  • Message types: Text, photos, booking requests, booking confirmations, system messages
  • Auto-translation: Messages are automatically translated using a neural machine translation service
  • Quick replies: Pre-written responses for common questions (check-in instructions, directions)
  • Smart suggestions: AI-generated reply suggestions based on conversation context
  • Read receipts: Blue checkmarks indicating message delivery and read status
  • Reporting: Messages can be reported for policy violations; the reporting user's messages are preserved

15. Photo Management & Verification

Photos are the single most important factor in a guest's booking decision. The platform stores hundreds of millions of photos and must serve them quickly with responsive images across all devices.

Photo Processing Pipeline

flowchart LR A[Upload Photo] --> B[Virus Scan] B --> C[EXIF Extraction] C --> D[Content Moderation
NSFW/Illegal Detection] D --> E[Face Detection
Privacy Check] E --> F[Generate Variants
2000px, 1000px, 500px, 200px] F --> G[Compress & Convert
WebP + AVIF] G --> H[Upload to CDN] H --> I[Update Database] I --> J[Trigger Search Index
Update]

Photo Storage Strategy

VariantSizeUse CaseFormat
OriginalFull resolutionBackup, reprocessingJPEG/PNG
Large2000px wideListing detail page heroWebP + AVIF
Medium1000px wideGallery, lightboxWebP + AVIF
Small500px wideSearch results gridWebP + AVIF
Thumbnail200px wideMap pins, carousel previewWebP + AVIF
BlurHash32 bytesProgressive placeholderBase64

16. Map-Based Search

Map-based search is a core feature that allows guests to explore listings geographically. The system must efficiently render hundreds of thousands of map pins and update results as the user pans and zooms.

Map Pin Clustering

When zoomed out, individual listings are clustered into aggregate pins to avoid visual clutter. The clustering algorithm uses a grid-based approach where pins are grouped into cells based on the current zoom level. Each cluster displays the number of listings and the average price.

Zoom LevelCell SizeTypical Cluster SizePin Shows
City-wide (zoom 10)~5km × 5km50-500 listingsPrice range for area
Neighborhood (zoom 13)~1km × 1km10-50 listingsListing count + avg price
Street (zoom 15)~300m × 300m3-10 listingsIndividual pins start appearing
Block (zoom 17+)Individual1 listing per pinPrice with photo thumbnail

Viewport-Based Loading

As the user pans the map, the frontend sends the current bounding box coordinates to the backend. The search service performs a geo-bounded query in Elasticsearch and returns only listings within the visible area. Results are cached using a geohash-based key that combines the viewport boundaries and search filters.

17. Recommendation Engine

The recommendation engine powers multiple surfaces: the homepage "Homes you might like," search result ranking, "Similar listings" on listing detail pages, and email recommendations. It uses a combination of collaborative filtering, content-based filtering, and graph-based recommendations.

Recommendation Signals

  • User behavior: Search history, saved listings, booking history, viewing patterns, wishlist additions
  • Listing features: Property type, amenities, location characteristics, price range, style
  • User preferences: Explicitly stated preferences, past booking patterns, price sensitivity
  • Social signals: Friends' bookings, trending in user's network
  • Contextual signals: Trip purpose (business vs leisure), group size, season
  • Graph relationships: Users who booked this also booked..., similar hosts, neighborhood associations

Recommendation Pipeline

The pipeline runs in two modes: batch (nightly recomputation of user embeddings and candidate generation) and real-time (on-demand scoring and re-ranking based on the current session context). The batch pipeline generates candidate sets of 1000 listings per user, and the real-time pipeline re-ranks the top 200 based on session signals before returning the final 20 results.

18. Host Dashboard & Tools

The host dashboard is the command center for hosts to manage their listings, track bookings, view earnings, and optimize their pricing. It requires aggregating data from multiple services into a responsive, real-time interface.

Dashboard Components

ComponentData SourcesUpdate Frequency
Booking CalendarCalendar Service, Booking ServiceReal-time via WebSocket
Earnings OverviewPayment Service, LedgerDaily aggregate, real-time pending
Performance MetricsReview Service, Search Service, Booking ServiceWeekly refresh
Guest MessagesMessaging ServiceReal-time via WebSocket
Reservation ManagerBooking ServiceReal-time
Listing HealthSearch ranking data, conversion metricsDaily
Market InsightsPricing Service, Competitor analysisWeekly
Tax DocumentsPayment Service, Tax ServiceAnnual / Quarterly

Host Payout Schedule

Host payouts are processed according to a configurable schedule. The default is 24 hours after guest check-in, but hosts can choose daily, weekly, or monthly payouts. Payout methods include bank transfer (ACH/SEPA), PayPal, and wire transfer for international hosts.

19. Cancellation & Refund Policy

Cancellation policies are a critical trust mechanism that protects both hosts and guests. The platform offers several standardized policies that hosts can choose from:

Standard Cancellation Policies

PolicyGuest RefundHost PenaltyBest For
Flexible100% refund up to 24h before check-inNone (host keeps payout if guest no-shows)Urban listings with high demand
Moderate100% refund up to 5 days before check-in50% of first night if within 7 daysMost listings
Strict50% refund up to 7 days before check-in50% of total if guest cancels within 7 daysRural/remote listings
Non-refundableNo refund (10% discount offered to guest)Full payoutHigh-demand listings
Long-term (28+ nights)30 days notice required, partial refund30 days notice required, partial penaltyMonthly/long-term stays

Extenuating Circumstances Policy

The platform maintains a separate extenuating circumstances policy that overrides the standard cancellation policy. This covers natural disasters, government-mandated restrictions, serious illness, and other qualifying events. Claims are reviewed by a specialized team with authority to issue full refunds regardless of the selected policy.

20. Database Sharding

At the scale of 7M+ listings and 540M+ bookings per year, a single MySQL instance cannot handle the load. We use application-level sharding with a clear shard key strategy.

Sharding Strategy

graph TB subgraph "Application Layer" App["Application
Services"] end subgraph "Shard Router" Router["Shard Router
(Consistent Hashing)"] end subgraph "Shard 0" DB0a["listings_0
bookings_0"] DB0b["availability_0"] end subgraph "Shard 1" DB1a["listings_1
bookings_1"] DB1b["availability_1"] end subgraph "Shard 2" DB2a["listings_2
bookings_2"] DB2b["availability_2"] end subgraph "Shard N" DBNa["listings_n
bookings_n"] DBNb["availability_n"] end App --> Router Router --> DB0a Router --> DB1a Router --> DB2a Router --> DBNa

Shard Key Selection

TableShard KeyRationaleNum Shards
listingslisting_idAll queries for a single listing hit one shard64
availability_calendarlisting_idCo-located with listings for JOIN efficiency64
bookingslisting_idMost queries are listing-centric (host dashboard, availability check)64
reviewslisting_idReviews are always queried by listing32
usersuser_idUser-centric queries (my trips, my listings)16
messagesconversation_idMessages grouped by conversation32 (Cassandra)

Cross-Shard Queries

Cross-shard queries are unavoidable for certain operations like "My Trips" (guest has bookings across multiple listing shards). We handle these through:

  • Denormalized views: A user_trips table that is co-located on the user's shard, updated asynchronously via Kafka events
  • Scatter-gather: For rare analytics queries, we query all shards in parallel and merge results (with pagination)
  • Read replicas per shard: Heavy read workloads are offloaded to read replicas to avoid impacting write performance

21. Caching Strategy

Caching is critical for performance at this scale. We use a multi-tier caching strategy with different TTLs and invalidation strategies for different data types.

Cache Hierarchy

LayerTechnologyWhat We CacheTTLInvalidation
L1 - BrowserService WorkerListing detail pages, search results5-30 minStale-while-revalidate
L2 - CDNCloudFrontStatic assets, listing photos, API responses1-24 hoursCache invalidation on update
L3 - ApplicationRedis ClusterHot search results, listing details, calendar data30s - 1hrEvent-driven invalidation
L4 - DatabaseMySQL Query CacheFrequent read queriesPer-queryAutomatic on write

Cache Keys Pattern

// Search results cache key
search:{lat_rounded}:{lng_rounded}:{checkin}:{checkout}:{guests}:{filters_hash}

// Example: search:40.71:-74.01:2026-08-01:2026-08-05:2:{price_min=50,price_max=300}
// TTL: 30 seconds (high churn, real-time availability changes)

// Listing detail cache key
listing:{listing_id}:{locale}:{currency}
// Example: listing:xyz789:en:USD
// TTL: 5 minutes (moderate staleness acceptable)

// Calendar cache key
calendar:{listing_id}:{year}:{month}
// Example: calendar:xyz789:2026:08
// TTL: 60 seconds (must be near-real-time for availability)

// Price quote cache key
price:{listing_id}:{checkin}:{checkout}:{guests}
// Example: price:xyz789:2026-08-01:2026-08-05:2
// TTL: 5 minutes (pricing can change with demand)

Cache Invalidation Strategy

We use an event-driven invalidation pattern. When a booking is made, the Booking Service publishes a BookingCreatedEvent to Kafka. Downstream cache consumers listen for these events and invalidate the relevant cache keys:

  • Booking created: Invalidate calendar cache, search cache for that area, listing availability
  • Listing updated: Invalidate listing detail cache, search cache
  • Review posted: Invalidate listing detail cache (rating changed)
  • Price changed: Invalidate price quote cache, search cache (price sorting affected)
Cache Stampede Prevention: When a cache key expires and is requested by multiple concurrent requests, we use a singleflight pattern where only one request goes to the database and the result is shared with all waiting requests. This prevents thundering herd problems on expensive queries.

22. Multi-Region Design

To serve users globally with low latency, the platform deploys across multiple AWS regions. The primary regions are US East (Virginia), EU West (Ireland), Asia Pacific (Tokyo), and South America (São Paulo).

Multi-Region Architecture

graph TB subgraph "US East (Primary)" US_EB["Application
Services"] US_DB["MySQL Primary
(Sharded)"] US_ES["Elasticsearch
Primary"] US_Redis["Redis
Cluster"] US_S3["S3 Primary"] end subgraph "EU West (Secondary)" EU_EB["Application
Services"] EU_DB["MySQL Read
Replica + Failover"] EU_ES["Elasticsearch
Replica"] EU_Redis["Redis
Cluster"] EU_S3["S3 Replica"] end subgraph "APAC (Secondary)" AP_EB["Application
Services"] AP_DB["MySQL Read
Replica + Failover"] AP_ES["Elasticsearch
Replica"] AP_Redis["Redis
Cluster"] AP_S3["S3 Replica"] end US_DB -->|Async Replication| EU_DB US_DB -->|Async Replication| AP_DB US_ES -->|Cross-Cluster Replication| EU_ES US_ES -->|Cross-Cluster Replication| AP_ES US_S3 -->|Cross-Region Replication| EU_S3 US_S3 -->|Cross-Region Replication| AP_S3

Data Replication Strategy

Data TypeReplication ModeLag ToleranceConflict Resolution
User profilesAsync (primary → replicas)< 1 secondLast-write-wins
ListingsAsync (primary → replicas)< 1 secondLast-write-wins
BookingsSync for writes (primary only), Async reads0 for writesSingle-primary, no conflict
CalendarAsync (primary → replicas)< 5 secondsCRDTs for availability
MessagesRegion-local (Cassandra multi-DC)< 1 secondEventual consistency
Search indexAsync (ES cross-cluster)< 30 secondsRe-index from source
PhotosAsync (S3 CRR)< 15 minutesN/A (immutable)

Regional Routing

The global load balancer (Route 53 with latency-based routing) directs users to the nearest region. For write operations that require primary-region consistency (like booking creation), the request is routed to the primary region regardless of the user's location. This adds ~50-100ms latency for distant users but ensures correctness.

23. Cost Estimation

ComponentSpecificationMonthly Cost (USD)
Compute (EKS)200+ pods across regions (c5.2xlarge)$120,000
MySQL (RDS)64 shards × r5.4xlarge + read replicas$200,000
Elasticsearch50-node cluster (r5.2xlarge)$150,000
Redis (ElastiCache)100-node cluster (r5.xlarge)$60,000
Cassandra30-node cluster (i3.2xlarge)$80,000
S3 Storage10PB+ photos + backups$25,000
CloudFront CDN500TB+ monthly transfer$40,000
Kafka (MSK)15-broker cluster (kafka.m5.2xlarge)$35,000
Monitoring & LoggingCloudWatch, Datadog, PagerDuty$25,000
Payment ProcessingStripe fees on GTV$200,000+
ML InfrastructureSageMaker, feature store, training$50,000
CDN for VideosListing video tours$15,000
Security & ComplianceWAF, Shield, compliance tools$20,000
Disaster RecoveryWarm standby in secondary regions$60,000
Total Infrastructure~$1,080,000/month
Note: These are rough estimates for a platform at Airbnb's scale. The actual costs would vary significantly based on reserved instance commitments, negotiated enterprise agreements, and actual usage patterns. For a startup, you could start with a fraction of this infrastructure (~$5,000-10,000/month) and scale up.

24. Interview Q&A (10+ Questions)

Q1: How do you prevent double-booking when two guests try to book the same listing for overlapping dates simultaneously?
We use database-level locking with SELECT FOR UPDATE on the availability calendar rows for the requested date range. This acquires row-level locks that prevent concurrent modifications. The transaction uses ISOLATION LEVEL SERIALIZABLE to ensure the entire booking creation is atomic. Additionally, we implement a reservation token system where a successful lock reserves the dates for 30 minutes while the guest completes payment. If payment fails, the reservation is released. For extreme throughput, we can layer a distributed lock (Redis SETNX on listing_id) before hitting the database to reduce contention.
Q2: How would you design the search system to handle real-time availability filtering while maintaining low latency?
We use a two-phase approach. Phase 1: Elasticsearch returns candidate listings based on geo, price, amenities, and other static filters — this is fast because ES handles these natively. Phase 2: We overlay real-time availability from a Redis-backed calendar cache. Only the top 200 candidates need availability checking, which can be done in a single Redis pipeline call (MGET for 200 keys). The Redis calendar cache is updated within 60 seconds of any booking or calendar change via Kafka events. This approach gives us sub-200ms latency while maintaining near-real-time accuracy.
Q3: How do you handle the dynamic pricing engine at scale? What are the trade-offs?
The pricing engine pre-computes prices for the next 365 days for each listing and caches them in Redis. Full recomputation runs nightly for all listings, with incremental updates triggered by demand changes, competitor price changes, or event additions (via Kafka). The main trade-off is between freshness and computation cost. Hot listings in high-demand areas get recomputed more frequently (every 4 hours) while long-tail listings may only recompute weekly. The ML model runs inference in near-real-time for the pricing API but training is batch (daily). We also allow hosts to set min/max bounds to prevent prices from going outside acceptable ranges.
Q4: How would you design the review system to be fair and resist manipulation?
We implement several anti-manipulation mechanisms: 1) Blind double review — neither party sees the other's review until both submit or the 14-day window expires. 2) Bayesian weighted average rating prevents listings with few reviews from having artificially high scores. 3) We detect review rings (groups of users who only review each other) using graph analysis. 4) ML models flag reviews with sentiment inconsistent with booking experience (e.g., negative review from a user who left a 5-star rating). 5) Verified stay badges only apply to actual completed bookings. 6) We monitor for unusual patterns like review timing (reviews posted immediately after booking without a stay) and cross-reference with calendar data.
Q5: How do you handle payment processing with split payments between platform and host, including holds and refunds?
We use Stripe Connect (or equivalent) for payment processing. The flow is: 1) When a guest books, we authorize the full amount on their card (hold). 2) The authorization is captured 24 hours after check-in. 3) Upon capture, the platform splits the payment — the host's share (typically 85%) is held for payout, and the platform fee (15%) is transferred to the platform account. 4) Host payouts are batched and processed according to their payout schedule. For refunds, we use the cancellation policy to calculate the refund amount, then issue a refund through the PSP. We maintain a double-entry financial ledger for auditability. Holds are essential for damage deposits and ensure funds are available before check-in.
Q6: How would you design the system to support map-based search with clustering at different zoom levels?
The frontend uses Mapbox GL JS or MapLibre for rendering. Map pins are loaded dynamically based on the current viewport bounding box. We use a geohash-based clustering approach: at low zoom levels (city-wide), listings are grouped into grid cells based on a truncated geohash prefix (e.g., 3 characters). Each cluster stores the count, center point, and average price. As the user zooms in, clusters split into smaller groups or individual pins. The backend serves a tiles API that returns pre-clustered pin data for the current viewport. Redis caches the tile data with viewport-based keys. For the initial viewport load, we query Elasticsearch with a geo_bounding_box filter and return up to 10,000 listings, which the frontend clusters client-side for smooth panning.
Q7: How do you handle the messaging system's real-time requirements while maintaining message history and searchability?
We use a dual-storage approach. Cassandra stores the full message history, partitioned by conversation_id with clustering by timestamp. This gives us efficient time-range reads for loading conversation history and linear write scalability. For real-time delivery, messages flow through a WebSocket gateway backed by Redis Pub/Sub. When a message is sent, it's written to Cassandra, then published to Redis Pub/Sub which fans out to connected WebSocket instances. The WebSocket layer is stateless and horizontally scalable. For message search (useful for finding addresses, booking references, etc.), we index message content in Elasticsearch with a 30-second delay. Media attachments are uploaded to S3 and their URLs are embedded in the message body.
Q8: How would you shard the database, and what are the challenges of cross-shard queries?
We shard by listing_id for listing-related tables and by user_id for user-related tables. This means most single-listing queries (detail, calendar, reviews, bookings for host) hit one shard. The main cross-shard challenge is the guest's "My Trips" view, which requires querying bookings across all listing shards. We solve this with a denormalized user_trips table co-located on the user's shard, updated asynchronously via Kafka events. Other cross-shard operations include search (handled by Elasticsearch, not MySQL), analytics (handled by a data warehouse with ETL), and global leaderboards. We use consistent hashing for shard routing with virtual nodes to handle rebalancing when adding shards. Each shard runs on dedicated RDS instances with read replicas.
Q9: How do you handle multi-currency pricing and payments across 220+ countries?
The pricing engine stores all base prices in the listing's home currency and converts to the guest's display currency in real-time using exchange rates from a reliable forex API (e.g., Open Exchange Rates). Exchange rates are cached for 1 hour and refreshed. The actual payment is processed in the guest's card currency through the PSP, which handles the conversion. Host payouts are made in the host's preferred payout currency. We maintain a currency conversion ledger that records the exchange rate at the time of booking to ensure financial accuracy. For price display, we show approximate amounts with a note about potential PSP conversion fees. We also support local payment methods like iDEAL (Netherlands), Boleto (Brazil), Alipay (China), and UPI (India).
Q10: How would you handle the cancellation and refund flow including partial refunds based on policy?
The cancellation flow is event-driven. When a cancellation is requested, the Cancellation Service: 1) Validates the cancellation against the policy (Flexible, Moderate, Strict, etc.). 2) Calculates the refund amount based on the timing relative to check-in and the policy terms. 3) Publishes a BookingCancelledEvent. 4) The Calendar Service listens and releases the dates back to available. 5) The Payment Service processes the refund through the PSP. 6) The Search Service re-indexes the listing with updated availability. 7) Notification Service sends cancellation confirmations to both parties. For disputes, we have an appeals workflow where either party can request a review. The financial ledger tracks all refund transactions for auditability. Host penalties for host-initiated cancellations are calculated separately and deducted from future payouts.
Q11: How do you ensure the platform handles flash sales or viral listings without degradation?
We implement several mechanisms for traffic spikes: 1) Auto-scaling with Kubernetes HPA that scales pods based on CPU and custom metrics (request queue depth). 2) Circuit breakers on downstream services (payment, calendar) to prevent cascading failures. 3) Rate limiting per user and per IP to prevent abuse. 4) Queue-based booking processing where requests are queued and processed sequentially to prevent overwhelming the database. 5) Pre-warming caches for trending listings identified by the recommendation engine. 6) Static asset serving through CDN absorbs the majority of read traffic. 7) Graceful degradation — if pricing service is slow, we serve the last-known price rather than blocking the entire flow.
Q12: How do you handle identity verification and prevent fake listings?
We use a multi-layered approach: 1) Government ID verification using a service like Jumio or Onfido that performs liveness detection and document authenticity checks. 2) Photo verification where hosts take a selfie that is compared to their ID photo using facial recognition. 3) Address verification via utility bill or bank statement upload. 4) New listing review — every new listing goes through a content moderation queue where human reviewers check photos, description, and pricing for consistency. 5) Ongoing monitoring using ML models that detect anomalies (suddenly changed listing details, price significantly below market, photos that don't match description). 6) Guest reviews serve as a crowdsourced verification layer. Superhost status requires maintaining high ratings, low cancellation rates, and fast response times.

25. Full C# Implementation (300+ Lines)

Below is a comprehensive C# implementation of the core booking engine, including the calendar service, pricing engine, booking state machine, and payment processing integration. This implementation follows clean architecture principles with dependency injection, async/await patterns, and proper error handling.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace AirbnbPlatform.Core.Entities
{
    public enum BookingStatus
    {
        Pending,
        Confirmed,
        CheckedIn,
        Completed,
        CancelledByGuest,
        CancelledByHost,
        Expired,
        Refunding,
        Refunded
    }

    public enum CancellationPolicy
    {
        Flexible,
        Moderate,
        Strict,
        NonRefundable,
        LongTerm
    }

    public enum BlockedReason
    {
        Available,
        HostBlocked,
        Booked,
        Maintenance,
        ExternalSync
    }

    public class Listing
    {
        public Guid Id { get; set; }
        public Guid HostId { get; set; }
        public string Title { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
        public string PropertyType { get; set; } = string.Empty;
        public string RoomType { get; set; } = string.Empty;
        public int MaxGuests { get; set; }
        public int Bedrooms { get; set; }
        public int Beds { get; set; }
        public int Bathrooms { get; set; }
        public decimal BasePrice { get; set; }
        public decimal? MinPrice { get; set; }
        public decimal? MaxPrice { get; set; }
        public decimal CleaningFee { get; set; }
        public string Currency { get; set; } = "USD";
        public double Latitude { get; set; }
        public double Longitude { get; set; }
        public string City { get; set; } = string.Empty;
        public string Country { get; set; } = string.Empty;
        public bool IsActive { get; set; }
        public bool InstantBook { get; set; }
        public CancellationPolicy CancellationPolicy { get; set; }
        public decimal ServiceFeePercent { get; set; } = 0.14m;
        public decimal HostPayoutPercent { get; set; } = 0.85m;
        public Host Host { get; set; } = null!;
        public ICollection<ListingPhoto> Photos { get; set; } = new List<ListingPhoto>();
        public ICollection<Review> Reviews { get; set; } = new List<Review>();
    }

    public class ListingPhoto
    {
        public Guid Id { get; set; }
        public Guid ListingId { get; set; }
        public string Url { get; set; } = string.Empty;
        public string ThumbnailUrl { get; set; } = string.Empty;
        public int SortOrder { get; set; }
        public bool IsVerified { get; set; }
        public DateTime UploadedAt { get; set; }
    }

    public class Host
    {
        public Guid Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Email { get; set; } = string.Empty;
        public bool IsSuperhost { get; set; }
        public decimal ResponseRate { get; set; }
        public decimal AcceptanceRate { get; set; }
        public string PreferredPayoutCurrency { get; set; } = "USD";
        public ICollection<Listing> Listings { get; set; } = new List<Listing>();
    }

    public class Guest
    {
        public Guid Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Email { get; set; } = string.Empty;
        public bool IsVerified { get; set; }
        public int TrustScore { get; set; }
    }

    public class AvailabilityCalendar
    {
        public Guid Id { get; set; }
        public Guid ListingId { get; set; }
        public DateTime Date { get; set; }
        public bool IsAvailable { get; set; }
        public decimal? OverridePrice { get; set; }
        public int MinNights { get; set; } = 1;
        public int MaxNights { get; set; } = 365;
        public BlockedReason BlockedReason { get; set; }
        public Listing Listing { get; set; } = null!;
    }

    public class Booking
    {
        public Guid Id { get; set; }
        public Guid ListingId { get; set; }
        public Guid GuestId { get; set; }
        public Guid HostId { get; set; }
        public DateTime CheckIn { get; set; }
        public DateTime CheckOut { get; set; }
        public int Guests { get; set; }
        public BookingStatus Status { get; set; }
        public decimal TotalPrice { get; set; }
        public decimal NightlyTotal { get; set; }
        public decimal CleaningFee { get; set; }
        public decimal ServiceFee { get; set; }
        public decimal OccupancyTax { get; set; }
        public decimal HostPayout { get; set; }
        public string Currency { get; set; } = "USD";
        public CancellationPolicy CancellationPolicy { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime? ConfirmedAt { get; set; }
        public DateTime? CancelledAt { get; set; }
        public string? CancellationReason { get; set; }
        public Listing Listing { get; set; } = null!;
        public Guest Guest { get; set; } = null!;
    }

    public class Review
    {
        public Guid Id { get; set; }
        public Guid BookingId { get; set; }
        public Guid ReviewerId { get; set; }
        public Guid ListingId { get; set; }
        public int Rating { get; set; }
        public string Comment { get; set; } = string.Empty;
        public DateTime CreatedAt { get; set; }
        public bool IsPublished { get; set; }
    }

    public class PriceQuote
    {
        public Guid ListingId { get; set; }
        public List<NightlyPrice> NightlyPrices { get; set; } = new();
        public decimal Subtotal { get; set; }
        public decimal CleaningFee { get; set; }
        public decimal ServiceFee { get; set; }
        public decimal OccupancyTax { get; set; }
        public decimal TotalPrice { get; set; }
        public string Currency { get; set; } = "USD";
        public DateTime CalculatedAt { get; set; }
    }

    public class NightlyPrice
    {
        public DateTime Date { get; set; }
        public decimal BasePrice { get; set; }
        public decimal AdjustedPrice { get; set; }
        public List<string> AppliedFactors { get; set; } = new();
    }

    public class PaymentRecord
    {
        public Guid Id { get; set; }
        public Guid BookingId { get; set; }
        public decimal Amount { get; set; }
        public string Currency { get; set; } = "USD";
        public string Status { get; set; } = "Pending";
        public string? PSPReference { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}

namespace AirbnbPlatform.Core.Interfaces
{
    using AirbnbPlatform.Core.Entities;

    public interface ICalendarService
    {
        Task<List<AvailabilityCalendar>> GetAvailabilityAsync(
            Guid listingId, DateTime start, DateTime end, CancellationToken ct);
        Task<bool> ReserveDatesAsync(
            Guid listingId, DateTime checkIn, DateTime checkOut, CancellationToken ct);
        Task ReleaseDatesAsync(
            Guid listingId, DateTime checkIn, DateTime checkOut, CancellationToken ct);
        Task ConfirmDatesAsync(
            Guid listingId, DateTime checkIn, DateTime checkOut, CancellationToken ct);
    }

    public interface IPricingEngine
    {
        Task<PriceQuote> CalculatePriceAsync(
            Guid listingId, DateTime checkIn, DateTime checkOut,
            int guests, CancellationToken ct);
    }

    public interface IPaymentService
    {
        Task<PaymentRecord> AuthorizePaymentAsync(
            Guid bookingId, decimal amount, string currency, CancellationToken ct);
        Task<PaymentRecord> CapturePaymentAsync(
            Guid paymentId, CancellationToken ct);
        Task<PaymentRecord> RefundPaymentAsync(
            Guid paymentId, decimal amount, string reason, CancellationToken ct);
    }

    public interface IEventPublisher
    {
        Task PublishAsync<T>(T eventData, CancellationToken ct);
    }

    public interface INotificationService
    {
        Task SendBookingConfirmationAsync(Booking booking, CancellationToken ct);
        Task SendCancellationNoticeAsync(Booking booking, string cancelledBy, CancellationToken ct);
    }
}

namespace AirbnbPlatform.Core.Services
{
    using AirbnbPlatform.Core.Entities;
    using AirbnbPlatform.Core.Interfaces;

    public class PricingEngine : IPricingEngine
    {
        private readonly ILogger<PricingEngine> _logger;

        public PricingEngine(ILogger<PricingEngine> logger)
        {
            _logger = logger;
        }

        public async Task<PriceQuote> CalculatePriceAsync(
            Guid listingId, DateTime checkIn, DateTime checkOut,
            int guests, CancellationToken ct)
        {
            // In production, this fetches from repository
            // Simplified for demonstration
            var listing = await GetListingAsync(listingId, ct);

            var nightlyPrices = new List<NightlyPrice>();
            var current = checkIn;

            while (current < checkOut)
            {
                var basePrice = listing.BasePrice;
                var adjustedPrice = basePrice;
                var factors = new List<string>();

                // Day of week adjustment
                var dowMultiplier = GetDayOfWeekMultiplier(current);
                adjustedPrice *= dowMultiplier;
                if (dowMultiplier != 1.0m)
                    factors.Add($"Weekend: x{dowMultiplier}");

                // Seasonal adjustment
                var seasonalMultiplier = GetSeasonalMultiplier(current);
                adjustedPrice *= seasonalMultiplier;
                if (seasonalMultiplier != 1.0m)
                    factors.Add($"Season: x{seasonalMultiplier}");

                // Event surge
                var eventMultiplier = GetEventMultiplier(current);
                adjustedPrice *= eventMultiplier;
                if (eventMultiplier != 1.0m)
                    factors.Add($"Event: x{eventMultiplier}");

                // Lead time
                var leadDays = (current - DateTime.UtcNow.Date).Days;
                var leadMultiplier = GetLeadTimeMultiplier(leadDays);
                adjustedPrice *= leadMultiplier;
                if (leadMultiplier != 1.0m)
                    factors.Add($"LeadTime: x{leadMultiplier}");

                // Guest count premium
                if (guests > 4)
                {
                    var guestMultiplier = 1.0m + (guests - 4) * 0.05m;
                    adjustedPrice *= guestMultiplier;
                    factors.Add($"Guests: x{guestMultiplier}");
                }

                // Min/Max enforcement
                if (listing.MinPrice.HasValue)
                    adjustedPrice = Math.Max(adjustedPrice, listing.MinPrice.Value);
                if (listing.MaxPrice.HasValue)
                    adjustedPrice = Math.Min(adjustedPrice, listing.MaxPrice.Value);

                // Round to nearest $5
                adjustedPrice = Math.Round(adjustedPrice / 5) * 5;

                nightlyPrices.Add(new NightlyPrice
                {
                    Date = current,
                    BasePrice = basePrice,
                    AdjustedPrice = adjustedPrice,
                    AppliedFactors = factors
                });

                current = current.AddDays(1);
            }

            var subtotal = nightlyPrices.Sum(p => p.AdjustedPrice);
            var cleaningFee = listing.CleaningFee;
            var serviceFee = Math.Round(subtotal * listing.ServiceFeePercent, 2);
            var occupancyTax = Math.Round(subtotal * 0.08m, 2);

            return new PriceQuote
            {
                ListingId = listingId,
                NightlyPrices = nightlyPrices,
                Subtotal = subtotal,
                CleaningFee = cleaningFee,
                ServiceFee = serviceFee,
                OccupancyTax = occupancyTax,
                TotalPrice = subtotal + cleaningFee + serviceFee + occupancyTax,
                Currency = listing.Currency,
                CalculatedAt = DateTime.UtcNow
            };
        }

        private decimal GetDayOfWeekMultiplier(DateTime date) => date.DayOfWeek switch
        {
            DayOfWeek.Friday => 1.15m,
            DayOfWeek.Saturday => 1.20m,
            DayOfWeek.Sunday => 1.05m,
            _ => 1.0m
        };

        private decimal GetSeasonalMultiplier(DateTime date)
        {
            var month = date.Month;
            return month switch
            {
                6 or 7 or 8 => 1.35m,   // Summer peak
                12 or 1 => 1.20m,        // Winter holidays
                3 or 4 => 1.10m,         // Spring break
                10 or 11 => 0.95m,       // Autumn shoulder
                _ => 1.0m                // Off-peak
            };
        }

        private decimal GetEventMultiplier(DateTime date)
        {
            // In production, queries event database for local events
            // Simplified mock logic
            return 1.0m;
        }

        private decimal GetLeadTimeMultiplier(int leadDays) => leadDays switch
        {
            <= 1 => 0.80m,    // Last-minute discount
            <= 3 => 0.90m,
            <= 7 => 0.95m,
            <= 30 => 1.0m,
            <= 90 => 1.05m,
            _ => 1.10m          // Far-out premium
        };

        private Task<Listing> GetListingAsync(Guid id, CancellationToken ct)
        {
            // Placeholder — in production, fetches from repository/cache
            return Task.FromResult(new Listing
            {
                Id = id,
                BasePrice = 150m,
                CleaningFee = 35m,
                Currency = "USD",
                MinPrice = 90m,
                MaxPrice = 450m,
                ServiceFeePercent = 0.14m,
                HostPayoutPercent = 0.85m
            });
        }
    }

    public class BookingService
    {
        private readonly ICalendarService _calendar;
        private readonly IPricingEngine _pricing;
        private readonly IPaymentService _payment;
        private readonly IEventPublisher _events;
        private readonly INotificationService _notifications;
        private readonly ILogger<BookingService> _logger;

        public BookingService(
            ICalendarService calendar,
            IPricingEngine pricing,
            IPaymentService payment,
            IEventPublisher events,
            INotificationService notifications,
            ILogger<BookingService> logger)
        {
            _calendar = calendar;
            _pricing = pricing;
            _payment = payment;
            _events = events;
            _notifications = notifications;
            _logger = logger;
        }

        public async Task<BookingResult> CreateBookingAsync(
            CreateBookingRequest request, CancellationToken ct)
        {
            _logger.LogInformation(
                "Creating booking for listing {ListingId}, {CheckIn} to {CheckOut}",
                request.ListingId, request.CheckIn, request.CheckOut);

            // Step 1: Validate date range
            if (request.CheckIn >= request.CheckOut)
                return BookingResult.Fail("Check-out must be after check-in");

            if ((request.CheckOut - request.CheckIn).TotalDays < 1)
                return BookingResult.Fail("Minimum stay is 1 night");

            if ((request.CheckOut - request.CheckIn).TotalDays > 365)
                return BookingResult.Fail("Maximum stay is 365 nights");

            if (request.Guests < 1)
                return BookingResult.Fail("At least 1 guest required");

            // Step 2: Calculate pricing
            var priceQuote = await _pricing.CalculatePriceAsync(
                request.ListingId, request.CheckIn, request.CheckOut,
                request.Guests, ct);

            // Step 3: Reserve dates (atomic operation)
            var reserved = await _calendar.ReserveDatesAsync(
                request.ListingId, request.CheckIn, request.CheckOut, ct);

            if (!reserved)
                return BookingResult.Fail(
                    "Selected dates are no longer available. " +
                    "Please choose different dates.");

            try
            {
                // Step 4: Process payment (authorization hold)
                var booking = new Booking
                {
                    Id = Guid.NewGuid(),
                    ListingId = request.ListingId,
                    GuestId = request.GuestId,
                    HostId = request.HostId,
                    CheckIn = request.CheckIn,
                    CheckOut = request.CheckOut,
                    Guests = request.Guests,
                    Status = BookingStatus.Pending,
                    TotalPrice = priceQuote.TotalPrice,
                    NightlyTotal = priceQuote.Subtotal,
                    CleaningFee = priceQuote.CleaningFee,
                    ServiceFee = priceQuote.ServiceFee,
                    OccupancyTax = priceQuote.OccupancyTax,
                    HostPayout = Math.Round(
                        priceQuote.Subtotal * request.HostPayoutPercent, 2),
                    Currency = priceQuote.Currency,
                    CancellationPolicy = request.CancellationPolicy,
                    CreatedAt = DateTime.UtcNow
                };

                var payment = await _payment.AuthorizePaymentAsync(
                    booking.Id, booking.TotalPrice, booking.Currency, ct);

                if (payment == null)
                {
                    await _calendar.ReleaseDatesAsync(
                        request.ListingId, request.CheckIn, request.CheckOut, ct);
                    return BookingResult.Fail("Payment authorization failed");
                }

                // Step 5: Determine if instant book or request-to-book
                if (request.IsInstantBook)
                {
                    booking.Status = BookingStatus.Confirmed;
                    booking.ConfirmedAt = DateTime.UtcNow;

                    await _calendar.ConfirmDatesAsync(
                        request.ListingId, request.CheckIn, request.CheckOut, ct);

                    await _payment.CapturePaymentAsync(payment.Id, ct);

                    await _events.PublishAsync(new BookingConfirmedEvent
                    {
                        BookingId = booking.Id,
                        ListingId = booking.ListingId,
                        GuestId = booking.GuestId,
                        CheckIn = booking.CheckIn,
                        CheckOut = booking.CheckOut
                    }, ct);

                    await _notifications.SendBookingConfirmationAsync(
                        booking, ct);
                }
                else
                {
                    await _events.PublishAsync(new BookingRequestedEvent
                    {
                        BookingId = booking.Id,
                        ListingId = booking.ListingId,
                        HostId = booking.HostId,
                        ExpiresAt = DateTime.UtcNow.AddHours(24)
                    }, ct);

                    await _notifications.SendBookingRequestAsync(
                        booking, ct);
                }

                _logger.LogInformation(
                    "Booking {BookingId} created successfully with status {Status}",
                    booking.Id, booking.Status);

                return BookingResult.Success(booking);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,
                    "Error creating booking for listing {ListingId}",
                    request.ListingId);

                await _calendar.ReleaseDatesAsync(
                    request.ListingId, request.CheckIn, request.CheckOut, ct);

                return BookingResult.Fail(
                    "An error occurred while processing your booking. " +
                    "Please try again.");
            }
        }

        public async Task<BookingResult> CancelBookingAsync(
            Guid bookingId, Guid userId, string reason, CancellationToken ct)
        {
            var booking = await GetBookingAsync(bookingId, ct);

            if (booking == null)
                return BookingResult.Fail("Booking not found");

            if (booking.Status != BookingStatus.Confirmed)
                return BookingResult.Fail(
                    "Only confirmed bookings can be cancelled");

            var isGuest = booking.GuestId == userId;
            var isHost = booking.HostId == userId;

            if (!isGuest && !isHost)
                return BookingResult.Fail("Unauthorized");

            // Calculate refund based on cancellation policy
            var refundAmount = CalculateRefund(
                booking, isGuest, DateTime.UtcNow);

            // Update booking status
            booking.Status = isGuest
                ? BookingStatus.CancelledByGuest
                : BookingStatus.CancelledByHost;
            booking.CancelledAt = DateTime.UtcNow;
            booking.CancellationReason = reason;

            // Release calendar dates
            await _calendar.ReleaseDatesAsync(
                booking.ListingId, booking.CheckIn, booking.CheckOut, ct);

            // Process refund
            if (refundAmount > 0)
            {
                booking.Status = BookingStatus.Refunding;
                await _payment.RefundPaymentAsync(
                    booking.Id, refundAmount,
                    $"Cancellation by {(isGuest ? "guest" : "host")}: {reason}",
                    ct);
            }

            // Publish event
            await _events.PublishAsync(new BookingCancelledEvent
            {
                BookingId = booking.Id,
                CancelledBy = isGuest ? "guest" : "host",
                RefundAmount = refundAmount,
                Reason = reason
            }, ct);

            await _notifications.SendCancellationNoticeAsync(
                booking, isGuest ? "guest" : "host", ct);

            return BookingResult.Success(booking);
        }

        private decimal CalculateRefund(
            Booking booking, bool isGuestCancellation, DateTime cancelTime)
        {
            var nightsCount = (booking.CheckOut - booking.CheckIn).Days;
            var nightsBeforeCheckIn =
                (booking.CheckIn - cancelTime).TotalDays;

            if (isGuestCancellation)
            {
                return booking.CancellationPolicy switch
                {
                    CancellationPolicy.Flexible =>
                        nightsBeforeCheckIn >= 1
                            ? booking.TotalPrice : booking.NightlyTotal * 0.5m,

                    CancellationPolicy.Moderate =>
                        nightsBeforeCheckIn >= 5
                            ? booking.TotalPrice : booking.NightlyTotal * 0.5m,

                    CancellationPolicy.Strict =>
                        nightsBeforeCheckIn >= 7
                            ? booking.TotalPrice * 0.5m : 0m,

                    CancellationPolicy.NonRefundable => 0m,

                    CancellationPolicy.LongTerm =>
                        nightsBeforeCheckIn >= 30
                            ? booking.TotalPrice
                            : booking.TotalPrice * 0.5m,

                    _ => 0m
                };
            }
            else
            {
                // Host cancellation — always full refund + host penalty
                return booking.TotalPrice;
            }
        }

        private Task<Booking?> GetBookingAsync(
            Guid id, CancellationToken ct)
        {
            // Placeholder for repository call
            return Task.FromResult<Booking?>(null);
        }
    }

    // Result types
    public class BookingResult
    {
        public bool IsSuccess { get; set; }
        public string? ErrorMessage { get; set; }
        public Booking? Booking { get; set; }

        public static BookingResult Success(Booking booking) => new()
        {
            IsSuccess = true,
            Booking = booking
        };

        public static BookingResult Fail(string error) => new()
        {
            IsSuccess = false,
            ErrorMessage = error
        };
    }

    public class CreateBookingRequest
    {
        public Guid ListingId { get; set; }
        public Guid GuestId { get; set; }
        public Guid HostId { get; set; }
        public DateTime CheckIn { get; set; }
        public DateTime CheckOut { get; set; }
        public int Guests { get; set; }
        public bool IsInstantBook { get; set; }
        public CancellationPolicy CancellationPolicy { get; set; }
        public decimal HostPayoutPercent { get; set; } = 0.85m;
    }

    // Event types for Kafka messaging
    public class BookingConfirmedEvent
    {
        public Guid BookingId { get; set; }
        public Guid ListingId { get; set; }
        public Guid GuestId { get; set; }
        public DateTime CheckIn { get; set; }
        public DateTime CheckOut { get; set; }
    }

    public class BookingRequestedEvent
    {
        public Guid BookingId { get; set; }
        public Guid ListingId { get; set; }
        public Guid HostId { get; set; }
        public DateTime ExpiresAt { get; set; }
    }

    public class BookingCancelledEvent
    {
        public Guid BookingId { get; set; }
        public string CancelledBy { get; set; } = string.Empty;
        public decimal RefundAmount { get; set; }
        public string Reason { get; set; } = string.Empty;
    }
}

This implementation totals over 300 lines and covers the core domain entities, interfaces, the pricing engine with multi-factor calculations, the booking creation flow with double-booking prevention, cancellation with policy-based refund calculation, and event publishing for async workflows.

26. Conclusion

Designing an accommodation platform like Airbnb is a masterclass in distributed systems engineering. The system must balance the conflicting requirements of real-time availability (no double-bookings) with massive read throughput (millions of search queries per second), while operating across 220+ countries with varying payment methods, regulations, and languages.

The key architectural insights from this design are:

  • Microservices with clear domain boundaries allow independent scaling of high-traffic services (search, calendar) from lower-traffic ones (reviews, user profiles)
  • Event-driven architecture with Kafka enables loose coupling and eventual consistency where appropriate, while synchronous calls are reserved for operations requiring immediate consistency
  • Multi-tier caching with Redis and CDN absorbs the majority of read traffic, protecting the database from overload
  • Database sharding by listing_id ensures that the most common query patterns (listing detail, calendar, host bookings) hit a single shard
  • The pricing engine combines rule-based and ML-based approaches, pre-computing prices to keep API latency low
  • Trust and safety is woven into every layer — from identity verification to fraud detection to content moderation

For system design interviews, focus on the booking state machine, calendar availability with atomic reservations, search with geo-filtering and real-time availability overlay, and the trade-offs between consistency and availability. These are the areas where interviewers will probe deepest.

Final Tip: When designing this system in an interview, start with the core booking flow and data model, then expand to search, pricing, and payments. Always discuss trade-offs — interviewers want to see that you understand the engineering decisions behind each architectural choice, not just the final diagram.

Sandip Mhaske

I’m a software developer exploring the depths of .NET, AWS, Angular, React, and digital entrepreneurship. Here, I decode complex problems, share insightful solutions, and navigate the evolving landscape of tech and finance.

Post a Comment

Previous Post Next Post