Machine Coding Problem

Property Rental (Airbnb)

macoAllmarketplacesearchbooking-consistency
Commonly Asked By:AirbnbBooking.comExpedia

Requirements & System Scope

Functional Scope (In-Scope)

  • Dynamic Calendar & Overlap Logic: Atomic reservation window check with custom multi-night discounts, weekend pricing spikes, and peak summer/winter holiday additions.
  • Striped Lock Concurrency Isolation: Locks individual listing schedules to eliminate system-wide bottlenecks and race conditions.
  • Geo-Spatial Radius Haversine Searching: Fast spherical math filtering to find listings inside maximum kilometer bounds.
  • Double-Blind Review release Strategy: Blocks guest/host feedback visibility until both are verified or the window closes.

Explicit Boundaries (Out-of-Scope)

  • Physical Payment Terminals: Stubbed with hold token captures without bank routing or merchant reconciliation systems.
  • Active WebSocket Chatting: skips live typing signals and websocket channel setups between host and guest.

Class Diagram & Entity Relationships

Relationship blueprint linking calendar blocks, geo-searches, and public/private reviews:

Loading...
  • Listing Entity: Stores basic specs including coordinates, overnight pricing, and head-count capacity limit rules.
  • Booking Node: Preserves date metrics, current status flags, and payment hold references safely.

Design Patterns & SOLID Principles

  • State Pattern: Controls transitions inside BookingStatus gracefully.
  • Strategy Pattern: Encapsulates DynamicPricingEngine price spike behaviors without editing core lists.
  • Single Responsibility Principle (SRP): Decouples physical range search and Haversine mapping logic from reviews collection.

Core Execution Workflows

Booking Lifecycle & Review Release Pipelines

  1. Two-Phase Booking Protocol:
    1. Acquire checks on target range constraints and overlaps.
    2. Secure date with PENDING_HOLD lock and fetch financial tokens.
    3. Process confirming step (switch to CONFIRMED) or execute automatic cancellation releases.
  2. Double-Blind Review Release:
    1. Register Guest or Host review in the non-public blindReviews dictionary on stay completion.
    2. Hold visibility until both parties have submitted.
    3. Transfer contents to the public reviews pipeline simultaneously.

Concurrency & Thread Safety Strategy

Preventing simultaneous double bookings under thundering herd holiday requests:

  • Synchronized Overlap Scopes: Lock individual listing calendars using striped locks to prevent conflicting date block writes.
  • Concurrent Repositories: Implements thread-safe data structures for listing collections to execute non-blocking, fast radius reads.

Complete Clean Code Blueprint

Production reference implementations demonstrating double-blind reviews, geo-radius checks, and atomic date reservations in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

enum BookingStatus {
    PENDING_HOLD, CONFIRMED, CANCELLED
}

class DateRange {
    private final LocalDate checkIn;
    private final LocalDate checkOut;

    public DateRange(LocalDate checkIn, LocalDate checkOut) {
        if (checkIn == null || checkOut == null || !checkIn.isBefore(checkOut)) {
            throw new IllegalArgumentException("Invalid dates: checkIn must be before checkOut.");
        }
        this.checkIn = checkIn;
        this.checkOut = checkOut;
    }

    public LocalDate getCheckIn() { return checkIn; }
    public LocalDate getCheckOut() { return checkOut; }

    public boolean overlaps(DateRange other) {
        return this.checkIn.isBefore(other.checkOut) && other.checkIn.isBefore(this.checkOut);
    }

    public long durationDays() {
        return ChronoUnit.DAYS.between(checkIn, checkOut);
    }
}

class Listing {
    private final String id;
    private final String hostId;
    private final String title;
    private final double latitude;
    private final double longitude;
    private final double basePricePerNight;
    private final int capacity;

    public Listing(String id, String hostId, String title, double latitude, double longitude, double basePricePerNight, int capacity) {
        this.id = id;
        this.hostId = hostId;
        this.title = title;
        this.latitude = latitude;
        this.longitude = longitude;
        this.basePricePerNight = basePricePerNight;
        this.capacity = capacity;
    }

    public String getId() { return id; }
    public String getHostId() { return hostId; }
    public String getTitle() { return title; }
    public double getLatitude() { return latitude; }
    public double getLongitude() { return longitude; }
    public double getBasePricePerNight() { return basePricePerNight; }
    public int getCapacity() { return capacity; }
}

class Booking {
    private final String id;
    private final String listingId;
    private final String guestId;
    private final DateRange dateRange;
    private final double totalAmount;
    private BookingStatus status;
    private String paymentHoldId;

    public Booking(String id, String listingId, String guestId, DateRange dateRange, double totalAmount) {
        this.id = id;
        this.listingId = listingId;
        this.guestId = guestId;
        this.dateRange = dateRange;
        this.totalAmount = totalAmount;
        this.status = BookingStatus.PENDING_HOLD;
    }

    public String getId() { return id; }
    public String getListingId() { return listingId; }
    public String getGuestId() { return guestId; }
    public DateRange getDateRange() { return dateRange; }
    public double getTotalAmount() { return totalAmount; }
    public synchronized BookingStatus getStatus() { return status; }
    public synchronized void setStatus(BookingStatus status) { this.status = status; }
    public synchronized String getPaymentHoldId() { return paymentHoldId; }
    public synchronized void setPaymentHoldId(String paymentHoldId) { this.paymentHoldId = paymentHoldId; }
}

class Review {
    private final String authorId;
    private final String targetId;
    private final int rating;
    private final String comment;

    public Review(String authorId, String targetId, int rating, String comment) {
        this.authorId = authorId;
        this.targetId = targetId;
        this.rating = rating;
        this.comment = comment;
    }

    public String getAuthorId() { return authorId; }
    public String getTargetId() { return targetId; }
    public int getRating() { return rating; }
    public String getComment() { return comment; }
}

class DynamicPricingEngine {
    public static double calculatePrice(Listing listing, DateRange range) {
        double total = 0.0;
        LocalDate current = range.getCheckIn();
        while (current.isBefore(range.getCheckOut())) {
            double price = listing.getBasePricePerNight();
            // Weekend surcharge (+20% for Friday/Saturday)
            if (current.getDayOfWeek() == java.time.DayOfWeek.FRIDAY || current.getDayOfWeek() == java.time.DayOfWeek.SATURDAY) {
                price *= 1.2;
            }
            // Seasonal surcharge (+15% during peak months June/December)
            int month = current.getMonthValue();
            if (month == 6 || month == 12) {
                price *= 1.15;
            }
            total += price;
            current = current.plusDays(1);
        }
        // Multi-night discount: 7+ nights gets 10% off
        long nights = range.durationDays();
        if (nights >= 7) {
            total *= 0.9;
        }
        return Math.round(total * 100.0) / 100.0;
    }
}

class PropertyRentalService {
    private final ConcurrentHashMap<String, Listing> listings = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, List<Booking>> listingBookings = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, ReentrantLock> listingLocks = new ConcurrentHashMap<>();
    
    // Mutual blind review repository: BookingID -> Map(AuthorID -> Review)
    private final ConcurrentHashMap<String, ConcurrentHashMap<String, Review>> blindReviews = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, CopyOnWriteArrayList<Review>> activeReviews = new ConcurrentHashMap<>();

    private ReentrantLock getListingLock(String listingId) {
        return listingLocks.computeIfAbsent(listingId, k -> new ReentrantLock());
    }

    public void addListing(Listing listing) {
        listings.put(listing.getId(), listing);
    }

    // Atomic date-range reservation utilizing per-listing lock striping
    public Booking createBookingRequest(String bookingId, String listingId, String guestId, LocalDate checkIn, LocalDate checkOut) {
        Listing listing = listings.get(listingId);
        if (listing == null) throw new IllegalArgumentException("Listing not found.");

        DateRange requestedRange = new DateRange(checkIn, checkOut);
        ReentrantLock lock = getListingLock(listingId);
        lock.lock();
        try {
            List<Booking> bookings = listingBookings.computeIfAbsent(listingId, k -> new ArrayList<>());

            // Check for date range overlap
            for (Booking existing : bookings) {
                if (existing.getStatus() != BookingStatus.CANCELLED) {
                    if (existing.getDateRange().overlaps(requestedRange)) {
                        throw new IllegalStateException("Requested dates overlap with existing booking.");
                    }
                }
            }

            double totalAmount = DynamicPricingEngine.calculatePrice(listing, requestedRange);
            Booking newBooking = new Booking(bookingId, listingId, guestId, requestedRange, totalAmount);
            bookings.add(newBooking);
            return newBooking;
        } finally {
            lock.unlock();
        }
    }

    public void confirmBooking(String bookingId, String paymentHoldId) {
        Booking booking = findBooking(bookingId);
        if (booking == null) throw new IllegalArgumentException("Booking not found");

        ReentrantLock lock = getListingLock(booking.getListingId());
        lock.lock();
        try {
            if (booking.getStatus() != BookingStatus.PENDING_HOLD) {
                throw new IllegalStateException("Booking is not in PENDING_HOLD state.");
            }
            booking.setPaymentHoldId(paymentHoldId);
            booking.setStatus(BookingStatus.CONFIRMED);
        } finally {
            lock.unlock();
        }
    }

    public void cancelBooking(String bookingId) {
        Booking booking = findBooking(bookingId);
        if (booking == null) throw new IllegalArgumentException("Booking not found");

        ReentrantLock lock = getListingLock(booking.getListingId());
        lock.lock();
        try {
            booking.setStatus(BookingStatus.CANCELLED);
        } finally {
            lock.unlock();
        }
    }

    private Booking findBooking(String bookingId) {
        for (List<Booking> list : listingBookings.values()) {
            synchronized (list) {
                for (Booking b : list) {
                    if (b.getId().equals(bookingId)) {
                        return b;
                    }
                }
            }
        }
        return null;
    }

    // Double-blind review submission logic
    public void submitReview(String bookingId, String authorId, String targetId, int rating, String comment) {
        Review review = new Review(authorId, targetId, rating, comment);
        
        ConcurrentHashMap<String, Review> bookingMap = blindReviews.computeIfAbsent(bookingId, k -> new ConcurrentHashMap<>());
        bookingMap.put(authorId, review);

        // When both guest and host reviews are posted, make both public simultaneously
        if (bookingMap.size() == 2) {
            for (Review r : bookingMap.values()) {
                activeReviews.computeIfAbsent(r.getTargetId(), k -> new CopyOnWriteArrayList<>()).add(r);
            }
            blindReviews.remove(bookingId);
        }
    }

    public List<Review> getPublicReviews(String targetId) {
        return activeReviews.getOrDefault(targetId, new CopyOnWriteArrayList<>());
    }

    public List<Listing> searchProperties(double userLat, double userLon, double maxDistanceKm, LocalDate checkIn, LocalDate checkOut, int guestsCount) {
        List<Listing> matched = new ArrayList<>();
        DateRange requestedRange = new DateRange(checkIn, checkOut);

        for (Listing listing : listings.values()) {
            if (listing.getCapacity() < guestsCount) continue;

            double dist = haversineDistance(userLat, userLon, listing.getLatitude(), listing.getLongitude());
            if (dist > maxDistanceKm) continue;

            boolean isAvailable = true;
            ReentrantLock lock = getListingLock(listing.getId());
            lock.lock();
            try {
                List<Booking> bookings = listingBookings.get(listing.getId());
                if (bookings != null) {
                    for (Booking b : bookings) {
                        if (b.getStatus() != BookingStatus.CANCELLED) {
                            if (b.getDateRange().overlaps(requestedRange)) {
                                isAvailable = false;
                                break;
                            }
                        }
                    }
                }
            } finally {
                lock.unlock();
            }

            if (isAvailable) {
                matched.add(listing);
            }
        }
        return matched;
    }

    private double haversineDistance(double lat1, double lon1, double lat2, double lon2) {
        final int R = 6371; // Radius of Earth in km
        double latDistance = Math.toRadians(lat2 - lat1);
        double lonDistance = Math.toRadians(lon2 - lon1);
        double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== STARTING AIRBNB PROPERTY RENTAL SYSTEM SIMULATION ===");
        PropertyRentalService service = new PropertyRentalService();

        Listing cozyCabin = new Listing("listing-1", "host-123", "Cozy Lakeside Cabin", 45.4215, -75.6972, 120.00, 4);
        Listing urbanLoft = new Listing("listing-2", "host-456", "Modern Downtown Loft", 45.4230, -75.6990, 200.00, 2);

        service.addListing(cozyCabin);
        service.addListing(urbanLoft);

        // 1. Search Properties Test
        System.out.println("--- Search Properties within 2km of center ---");
        List<Listing> searchResults = service.searchProperties(
            45.4210, -75.6960, 2.0, 
            LocalDate.of(2026, 6, 1), LocalDate.of(2026, 6, 8), 2
        );
        for (Listing l : searchResults) {
            System.out.println("  Found: " + l.getTitle() + " at Base Price: $" + l.getBasePricePerNight());
        }

        // 2. Concurrency Booking Race Test (Overlapping dates on same listing)
        System.out.println("\n--- Concurrent Booking Race Test ---");
        ExecutorService executor = Executors.newFixedThreadPool(3);
        CountDownLatch latch = new CountDownLatch(1);

        Runnable guestAttempt1 = () -> {
            try {
                latch.await();
                Booking b = service.createBookingRequest("book-100", "listing-1", "guest-abc",
                    LocalDate.of(2026, 6, 1), LocalDate.of(2026, 6, 5));
                System.out.println("  [SUCCESS] Guest ABC booked: " + b.getId() + " total amount $" + b.getTotalAmount());
                service.confirmBooking(b.getId(), "hold-token-xyz");
                System.out.println("  [SUCCESS] Guest ABC payment confirmed.");
            } catch (Exception e) {
                System.out.println("  [FAILED] Guest ABC booking failed: " + e.getMessage());
            }
        };

        Runnable guestAttempt2 = () -> {
            try {
                latch.await();
                Booking b = service.createBookingRequest("book-101", "listing-1", "guest-def",
                    LocalDate.of(2026, 6, 3), LocalDate.of(2026, 6, 8)); // Overlapping!
                System.out.println("  [SUCCESS] Guest DEF booked: " + b.getId());
            } catch (Exception e) {
                System.out.println("  [FAILED] Guest DEF booking failed: " + e.getMessage());
            }
        };

        executor.submit(guestAttempt1);
        executor.submit(guestAttempt2);

        latch.countDown();
        executor.shutdown();
        executor.awaitTermination(2, TimeUnit.SECONDS);

        // 3. Double-blind review release test
        System.out.println("\n--- Double-Blind Review Release Test ---");
        System.out.println("Submitting Guest Review for stay book-100...");
        service.submitReview("book-100", "guest-abc", "host-123", 5, "Amazing host and beautiful view!");
        System.out.println("Checking public reviews for host-123: " + service.getPublicReviews("host-123").size() + " reviews.");

        System.out.println("Submitting Host Review for stay book-100...");
        service.submitReview("book-100", "host-123", "guest-abc", 5, "Very polite guest, left place clean.");
        System.out.println("Both submitted! Public reviews for host-123: " + service.getPublicReviews("host-123").size());
        for (Review r : service.getPublicReviews("host-123")) {
            System.out.println("  - Review: Rating " + r.getRating() + ", " + r.getComment());
        }

        System.out.println("=== AIRBNB PROPERTY RENTAL SYSTEM SIMULATION COMPLETE ===");
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...