← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›LLD Interview Prep›LLD
📅

Design a Meeting Scheduler

Book rooms and time slots with overlap detection — classic Google, Microsoft, and Amazon LLD for calendar APIs and conflict resolution.

📊 Medium⏱ 35–45 min🏢 Google · Microsoft · Amazon · Meta · Salesforce · most calendar-adjacent product loops
🎯 Interview goal

Model User, Room, Meeting, and TimeSlot. SchedulerService validates no overlapping bookings for room and attendees, then persists the meeting. Core trick: `TimeSlot.overlaps(other)` for interval intersection.

📋

The interview question

“Design a meeting scheduler.” Users book a room (or virtual slot) for a start/end time with a list of attendees. Reject if the room or any participant is already busy.

Tests interval overlap logic, clean domain modeling, and a small service layer — not distributed calendar sync.

💡
Clarify single timezone, in-memory v1, and one building before coding. Mention Google Calendar sync as an extension only.
❓

Clarifying questions (ask before coding)

1Book by room, by user calendar, or both?
Why askUsually both — room conflict + attendee conflict checks.
2Can meetings be recurring?
Why askv1: single instance; recurring = expand series or RRULE extension.
3Cancel / reschedule APIs?
Why askAdd cancel(meetingId) and optional reschedule — shows lifecycle thinking.
4Room capacity vs attendee count?
Why askValidate attendees.size() <= room.capacity on book.
5Timezone handling?
Why askStore Instant or UTC; v1 assume single TZ to save time.
✅

Requirements & assumptions (state aloud)

  • schedule(user, room, slot, attendees) → Meeting or throw if conflict.
  • TimeSlot: half-open or closed interval; consistent overlap rule everywhere.
  • Conflict: same room double-booked OR any attendee has overlapping meeting.
  • cancel(meetingId): remove from room and user calendars.
  • v1: in-memory maps; no persistence, notifications, or video links.
🧩

High-level design

User holds a list of scheduled Meeting references (or IDs). Room holds its bookings. SchedulerService is the single entry point.

On schedule: build TimeSlot, verify room free, verify each attendee free, check capacity, then commit atomically (synchronized block in v1).

  1. TimeSlot — start/end + overlaps()
  2. Meeting — id, organizer, room, slot, attendees
  3. SchedulerService — schedule / cancel / list
Meeting scheduler flow
User → SchedulerService → Meeting, Room, TimeSlot
📐

UML class diagram (optional)

Draw SchedulerService at center with associations to Room and User — under 5 minutes on whiteboard.

Meeting scheduler UML
Service orchestrates User, Room, Meeting, TimeSlot
🎨

Design patterns used (and why)

PatternWhereWhy (interview soundbite)
🏛️FacadeSchedulerServiceOne API hides room lookup, overlap checks, and calendar updates.
📦Value objectTimeSlotImmutable interval; overlap logic lives in one place.
🗃️Repository (light)Maps for users/rooms/meetingsSwap in DB later without changing service contract.
💻

Full solution — domain model

1. TimeSlot & Meetingjava
import java.time.LocalDateTime;
import java.util.*;

public record TimeSlot(LocalDateTime start, LocalDateTime end) {
    // Validates start is strictly before end at construction
    public TimeSlot {
        if (!start.isBefore(end))
            throw new IllegalArgumentException("start must be before end");
    }

    // True if this interval intersects other (inclusive boundaries)
    public boolean overlaps(TimeSlot other) {
        return this.start.isBefore(other.end) && other.start.isBefore(this.end);
    }
}

public class Meeting {
    private final String id;
    private final User organizer;
    private final Room room;
    private final TimeSlot slot;
    private final List<User> attendees;

    // Immutable meeting aggregate once scheduled
    public Meeting(String id, User organizer, Room room, TimeSlot slot, List<User> attendees) {
        this.id = id;
        this.organizer = organizer;
        this.room = room;
        this.slot = slot;
        this.attendees = List.copyOf(attendees);
    }

    // Unique meeting identifier for cancel/reschedule
    public String getId() { return id; }

    // User who created the booking
    public User getOrganizer() { return organizer; }

    // Physical or virtual room reserved for this meeting
    public Room getRoom() { return room; }

    // Start/end window of the meeting
    public TimeSlot getSlot() { return slot; }

    // All participants including organizer if listed
    public List<User> getAttendees() { return attendees; }
}
2. User & Roomjava
public class User {
    private final String id;
    private final String name;
    private final List<Meeting> calendar = new ArrayList<>();

    // Identity for conflict checks and ownership
    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // Stable user id used as map key
    public String getId() { return id; }

    // Display name for logs and UI
    public String getName() { return name; }

    // Read-only view of this user's scheduled meetings
    public List<Meeting> getCalendar() { return Collections.unmodifiableList(calendar); }

    // Adds meeting to personal calendar after successful book
    void addMeeting(Meeting meeting) {
        calendar.add(meeting);
    }

    // Removes meeting on cancel; returns false if not found
    boolean removeMeeting(String meetingId) {
        return calendar.removeIf(m -> m.getId().equals(meetingId));
    }

    // True if any existing meeting overlaps the requested slot
    boolean hasConflict(TimeSlot slot) {
        return calendar.stream().anyMatch(m -> m.getSlot().overlaps(slot));
    }
}

public class Room {
    private final String id;
    private final int capacity;
    private final List<Meeting> bookings = new ArrayList<>();

    // Room identity and max attendees allowed
    public Room(String id, int capacity) {
        this.id = id;
        this.capacity = capacity;
    }

    // Room id for booking requests
    public String getId() { return id; }

    // Maximum attendees this room supports
    public int getCapacity() { return capacity; }

    // All meetings currently booked in this room
    public List<Meeting> getBookings() { return Collections.unmodifiableList(bookings); }

    // Registers a confirmed meeting on the room calendar
    void addBooking(Meeting meeting) {
        bookings.add(meeting);
    }

    // Drops a cancelled meeting from room schedule
    void removeBooking(String meetingId) {
        bookings.removeIf(m -> m.getId().equals(meetingId));
    }

    // True if room is already occupied during slot
    boolean isAvailable(TimeSlot slot) {
        return bookings.stream().noneMatch(m -> m.getSlot().overlaps(slot));
    }
}
⚙️

Full solution — SchedulerService

3. SchedulerServicejava
public class SchedulerService {
    private final Map<String, User> users = new HashMap<>();
    private final Map<String, Room> rooms = new HashMap<>();
    private final Map<String, Meeting> meetings = new HashMap<>();

    // Registers a user for scheduling lookups
    public void registerUser(User user) {
        users.put(user.getId(), user);
    }

    // Registers a bookable room
    public void registerRoom(Room room) {
        rooms.put(room.getId(), room);
    }

    // Books room and updates all attendee calendars; throws on conflict
    public synchronized Meeting schedule(
            User organizer, Room room, TimeSlot slot, List<User> attendees) {
        if (attendees.size() > room.getCapacity())
            throw new IllegalStateException("Attendee count exceeds room capacity");
        if (!room.isAvailable(slot))
            throw new IllegalStateException("Room not available");
        for (User attendee : attendees) {
            User registered = users.get(attendee.getId());
            if (registered == null)
                throw new IllegalArgumentException("Unknown user: " + attendee.getId());
            if (registered.hasConflict(slot))
                throw new IllegalStateException("User busy: " + registered.getName());
        }

        String meetingId = UUID.randomUUID().toString();
        Meeting meeting = new Meeting(meetingId, organizer, room, slot, attendees);
        room.addBooking(meeting);
        for (User attendee : attendees) {
            users.get(attendee.getId()).addMeeting(meeting);
        }
        meetings.put(meetingId, meeting);
        return meeting;
    }

    // Removes meeting from room and every attendee calendar
    public synchronized void cancel(String meetingId) {
        Meeting meeting = meetings.remove(meetingId);
        if (meeting == null) return;
        meeting.getRoom().removeBooking(meetingId);
        for (User attendee : meeting.getAttendees()) {
            users.get(attendee.getId()).removeMeeting(meetingId);
        }
    }

    // Returns all meetings for a user sorted by start time
    public List<Meeting> getMeetingsForUser(String userId) {
        User user = users.get(userId);
        if (user == null) return List.of();
        return user.getCalendar().stream()
            .sorted(Comparator.comparing(m -> m.getSlot().start()))
            .toList();
    }
}
4. Demojava
public class MeetingSchedulerDemo {
    // Books two slots; second overlapping booking should fail
    public static void main(String[] args) {
        SchedulerService svc = new SchedulerService();
        User alice = new User("u1", "Alice");
        User bob = new User("u2", "Bob");
        Room roomA = new Room("r1", 10);
        svc.registerUser(alice);
        svc.registerUser(bob);
        svc.registerRoom(roomA);

        TimeSlot morning = new TimeSlot(
            LocalDateTime.of(2026, 5, 26, 9, 0),
            LocalDateTime.of(2026, 5, 26, 10, 0));
        Meeting m1 = svc.schedule(alice, roomA, morning, List.of(alice, bob));
        System.out.println("Booked: " + m1.getId());

        TimeSlot overlap = new TimeSlot(
            LocalDateTime.of(2026, 5, 26, 9, 30),
            LocalDateTime.of(2026, 5, 26, 10, 30));
        try {
            svc.schedule(bob, roomA, overlap, List.of(bob));
        } catch (IllegalStateException e) {
            System.out.println("Conflict: " + e.getMessage());
        }
    }
}
🎤

While you code — questions interviewers ask

🚀

Extensions & follow-ups

  • Reschedule — cancel + schedule in one transaction.
  • Working hours / buffer between meetings.
  • Priority booking for executives (override with audit log).
  • External calendar sync (Google/Outlook) — HLD with webhooks.
⚠️

Common mistakes (fail signals)

  • Wrong overlap formula (start1 <= start2 && end1 >= end2 misses partial overlaps).
  • Checking room but forgetting attendee calendars.
  • Not validating capacity or unknown user ids.
  • Mutable TimeSlot shared and modified after book.
🧾

60-second cheat sheet

OverlapstartA < endB && startB < endA
ChecksRoom free · each attendee free · capacity
Core typesUser, Room, Meeting, TimeSlot, SchedulerService
PatternFacade service + value object TimeSlot
Concurrencysynchronized schedule or DB lock
🔗

More LLD problems