Machine Coding Problem

Meeting Scheduler

maco30maco60macoAllproductivityconcurrencyinterval-trees
Commonly Asked By:MicrosoftGoogleUberGoldman Sachs

Requirements & System Scope

Functional Scope (In-Scope)

  • Dynamic Room Booking: Book meeting rooms for standard dynamic time intervals (start/end timestamp epoch).
  • Overlapping Check: Strict overlap check to prevent double-booking rooms.
  • Search Availability: Return rooms matching minimum attendance size constraints that are free during requested slots.
  • Concurrent Protection: Process concurrent booking calls gracefully, maintaining complete ticket/reservation consistency.

Explicit Boundaries (Out-of-Scope)

  • No Dynamic Recurrence Logic: Recurring reservation rules (e.g. daily/weekly repeating meetings) are omitted to focus on structural interval scheduling.
  • No Room Equipment Configuration: Ignores specific equipment requests (projectors, smart boards, audio accessories).

Class Diagram & Entity Relationships

Structured structural layout showing decoupled domain scheduling objects:

Loading...
  • Value Object (TimeSlot): TimeSlot acts as a pure value object encapsulating mathematical interval overlaps, preventing logic leaks into parent classes.
  • Decoupled Locks: Fine-grained locks live inside individual Room instances rather than global orchestrators, maximizing performance during concurrent non-colliding reservations.

Design Patterns & SOLID Principles

  • Strategy Pattern (Room Selection Strategy): Decouples room selection/allocation algorithms (e.g. BestFitRoomSelectionStrategy vs FirstFitRoomSelectionStrategy) from the core MeetingScheduler orchestrator class, enabling runtime swappability and adhering to the Open-Closed Principle (OCP).
  • Single Responsibility Principle (SRP):TimeSlot solely evaluates time intersections. Room handles dynamic occupancy sets and thread synchronization. MeetingScheduler orchestrates room lists, delegating matching and selection to pluggable strategy instances.
  • State Encapsulation (ReentrantLock): Reentrant locks protect internal booking structures inside each room, encapsulating the transaction safely.

Core Execution Workflows

1. Room Allocation Flow

  1. Client invokes scheduleMeeting(organizer, attendees, timeSlot).
  2. Scheduler iterates through room set, sorting matching entries by size capacity.
  3. For every qualified room, a booking reservation transaction is attempted:
    1. Acquire the targeted room's localized Reentrant lock.
    2. Evaluate interval intersections: start1 < end2 && start2 < end1.
    3. If collision occurs, release lock immediately and check next room.
    4. Otherwise, create Booking, update occupancy records, release lock, and return.
  4. If all matching rooms are blocked, return an explicit error notification.

Concurrency & Thread Safety Strategy

The critical concurrency vulnerability occurs when multiple threads attempt to book the **same room** for overlapping intervals simultaneously. This can lead to double-bookings if the check and insertion are not performed atomically:

  • Fine-Grained Reentrant Locks: Locking individual rooms instead of the entire scheduler allows parallel booking transactions to succeed concurrently across different rooms.
  • Atomic Checkout Check: The check-then-act logic (checking overlap and inserting booking) is entirely enclosed inside the lock's scope.

Complete Clean Code Blueprint

Clean reference designs showcasing operational patterns and thread-safety in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

class TimeSlot {
    private final long start;
    private final long end;

    public TimeSlot(long start, long end) {
        if (start >= end) {
            throw new IllegalArgumentException("Start time must be strictly before end time.");
        }
        if (start < 0) {
            throw new IllegalArgumentException("Time values cannot be negative.");
        }
        this.start = start;
        this.end = end;
    }

    public long getStart() { return start; }
    public long getEnd() { return end; }

    public boolean overlaps(TimeSlot other) {
        return this.start < other.end && other.start < this.end;
    }

    @Override
    public String toString() {
        return "[" + start + " - " + end + "]";
    }
}

class Booking {
    private final String id;
    private final Room room;
    private final TimeSlot timeSlot;
    private final String organizer;

    public Booking(String id, Room room, TimeSlot timeSlot, String organizer) {
        this.id = Objects.requireNonNull(id);
        this.room = Objects.requireNonNull(room);
        this.timeSlot = Objects.requireNonNull(timeSlot);
        this.organizer = Objects.requireNonNull(organizer);
    }

    public String getId() { return id; }
    public Room getRoom() { return room; }
    public TimeSlot getTimeSlot() { return timeSlot; }
    public String getOrganizer() { return organizer; }
}

class Room {
    private final String id;
    private final int capacity;
    private final List<Booking> bookings = new ArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Room(String id, int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("Capacity must be positive.");
        }
        this.id = Objects.requireNonNull(id);
        this.capacity = capacity;
    }

    public String getId() { return id; }
    public int getCapacity() { return capacity; }

    public boolean isAvailable(TimeSlot slot) {
        lock.lock();
        try {
            for (Booking existing : bookings) {
                if (existing.getTimeSlot().overlaps(slot)) {
                    return false;
                }
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    public boolean book(Booking booking) {
        lock.lock();
        try {
            for (Booking existing : bookings) {
                if (existing.getTimeSlot().overlaps(booking.getTimeSlot())) {
                    return false;
                }
            }
            bookings.add(booking);
            return true;
        } finally {
            lock.unlock();
        }
    }

    public List<Booking> getBookings() {
        lock.lock();
        try {
            return new ArrayList<>(bookings);
        } finally {
            lock.unlock();
        }
    }
}

interface RoomSelectionStrategy {
    Room selectRoom(List<Room> rooms, int attendees, TimeSlot slot);
}

class BestFitRoomSelectionStrategy implements RoomSelectionStrategy {
    @Override
    public Room selectRoom(List<Room> rooms, int attendees, TimeSlot slot) {
        List<Room> candidateRooms = new ArrayList<>();
        for (Room room : rooms) {
            if (room.getCapacity() >= attendees) {
                candidateRooms.add(room);
            }
        }
        candidateRooms.sort(Comparator.comparingInt(Room::getCapacity));
        for (Room room : candidateRooms) {
            if (room.isAvailable(slot)) {
                return room;
            }
        }
        return null;
    }
}

class FirstFitRoomSelectionStrategy implements RoomSelectionStrategy {
    @Override
    public Room selectRoom(List<Room> rooms, int attendees, TimeSlot slot) {
        for (Room room : rooms) {
            if (room.getCapacity() >= attendees && room.isAvailable(slot)) {
                return room;
            }
        }
        return null;
    }
}

class MeetingScheduler {
    private final List<Room> rooms = new CopyOnWriteArrayList<>();
    private final RoomSelectionStrategy roomSelectionStrategy;

    public MeetingScheduler(RoomSelectionStrategy strategy) {
        this.roomSelectionStrategy = Objects.requireNonNull(strategy);
    }

    public void addRoom(Room room) {
        rooms.add(Objects.requireNonNull(room));
    }

    public List<Room> findAvailableRooms(TimeSlot slot, int minCapacity) {
        List<Room> available = new ArrayList<>();
        for (Room room : rooms) {
            if (room.getCapacity() >= minCapacity && room.isAvailable(slot)) {
                available.add(room);
            }
        }
        return available;
    }

    public Booking scheduleMeeting(String organizer, int attendees, TimeSlot slot) {
        Room room = roomSelectionStrategy.selectRoom(rooms, attendees, slot);
        if (room != null) {
            String bookingId = "BKG-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
            Booking booking = new Booking(bookingId, room, slot, organizer);
            if (room.book(booking)) {
                System.out.println("[Scheduler] Success: Room " + room.getId() + " booked for " + organizer + " " + slot);
                return booking;
            }
        }
        System.out.println("[Scheduler] Fail: No room available for " + organizer + " at slot " + slot + " for " + attendees + " attendees.");
        return null;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MeetingScheduler scheduler = new MeetingScheduler(new BestFitRoomSelectionStrategy());
        scheduler.addRoom(new Room("Room-A", 5));
        scheduler.addRoom(new Room("Room-B", 10));
        scheduler.addRoom(new Room("Room-C", 15));

        System.out.println("--- Starting Concurrent Meeting Booking Simulation ---");
        ExecutorService executor = Executors.newFixedThreadPool(4);

        TimeSlot slot1 = new TimeSlot(10, 12);
        TimeSlot slot2 = new TimeSlot(11, 13); // Overlaps with slot1

        // Task 1: Book Room for 4 people at 10-12
        Runnable task1 = () -> scheduler.scheduleMeeting("Alice", 4, slot1);

        // Task 2: Book Room for 8 people at 10-12 (should go to Room-B since Room-A is for 5)
        Runnable task2 = () -> scheduler.scheduleMeeting("Bob", 8, slot1);

        // Task 3: Book Room for 4 people at 11-13 (overlaps with slot1, should go to Room-C if A is taken)
        Runnable task3 = () -> scheduler.scheduleMeeting("Charlie", 4, slot2);

        // Task 4: Duplicate Alice's slot attempt by Dave (should fail if Alice gets Room-A first)
        Runnable task4 = () -> scheduler.scheduleMeeting("Dave", 4, slot1);

        executor.submit(task1);
        executor.submit(task2);
        executor.submit(task3);
        executor.submit(task4);

        executor.shutdown();
        executor.awaitTermination(2, TimeUnit.SECONDS);
        System.out.println("--- Booking Simulation Completed ---");
    }
}

๐Ÿ’ฌ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...