Machine Coding Problem

Property Rental (Airbnb)

macoAllmarketplacesearchbooking-consistency
Commonly Asked By:AirbnbBooking.comExpedia

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.

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 ===");
    }
}