Meeting Room Scheduler
Prefer to learn one decision at a time?
Try questions, reveal the reasoning, trace the code, and finish with the simulator.
Design a meeting-room scheduler that finds a room with enough space and equipment, prevents time conflicts, and makes the room reusable after cancellation.

1. Requirements (~5 minutes)
The interview prompt is usually short:
“Design a meeting-room scheduler.”
We all understand meetings and rooms, but that familiarity can hide unanswered questions. Does the caller choose a room? Does a meeting ending at 10:00 conflict with one starting at 10:00? Are attendee calendars included? Can anybody cancel another person's meeting?
Do not begin with a Room class. First turn the vague prompt into actions whose results both you and the interviewer can predict.
Use these four question groups every time
What must a user be able to do?
When does an action succeed, fail, or change state?
Which invalid actions must we reject?
What should we deliberately not build?
Ask these questions
AskWhich actions must the first version support?Show answerHide answer
Interviewer answer
An organizer submits one meeting request. The system chooses a room and returns a meeting ID. The same organizer may later cancel it.
AskWhat makes a room suitable before we check its calendar?Show answerHide answer
Interviewer answer
A request contains an attendee count and zero or more required equipment values. A room needs enough capacity and every required item.
AskWho chooses the room, and which suitable room wins?Show answerHide answer
Interviewer answer
The system chooses the smallest suitable available room. If capacities tie, choose the lower room ID. Keep the policy replaceable.
AskDoes a meeting from 09:00 to 10:00 block a meeting starting exactly at 10:00?Show answerHide answer
Interviewer answer
No. Treat every slot as [start, end): start is included and end is excluded.
AskWhat happens when no room satisfies every rule?Show answerHide answer
Interviewer answer
Return NO_ROOM_AVAILABLE. Do not create a meeting ID or reserve any room.
AskWho may cancel a meeting, and what does cancellation change?Show answerHide answer
Interviewer answer
Only the organizer may cancel. An unknown ID or wrong organizer is rejected without changing the schedule.
AskWhich time-zone, attendee, recurrence, storage, and concurrency rules are in scope?Show answerHide answer
Interviewer answer
Use one office time zone and pre-created rooms. Calls reach one in-memory process sequentially. Leave recurring meetings, attendee calendars, persistence, and simultaneous callers for extensions.
Confirm the specification
Confirmed specification
8 requirements · 8 boundaries
The first version must
- 1.Rooms are pre-created with a unique ID, name, capacity, and equipment set.
- 2.A request contains a title, organizer ID, attendee count, required equipment, start time, and end time.
- 3.A valid time slot uses [start, end): start must be before end, and back-to-back meetings are allowed.
- 4.A room is suitable only when it has enough capacity and all required equipment.
- 5.A room is available only when the requested slot overlaps none of its existing meetings.
- 6.Choose the available suitable room with the smallest capacity; break ties by room ID.
- 7.If no room qualifies, return NO_ROOM_AVAILABLE and change nothing.
- 8.Only the organizer may cancel a known meeting; successful cancellation frees its room and slot.
Not building in this version8 boundaries
- Recurring meetings
- Attendee calendars and invitations
- Time-zone conversion
- Room setup or cleanup buffers
- Persistent storage
- Concurrent or multi-server requests
- Notifications and email
- Changing an existing meeting in place
2. Entities and relationships (~3 minutes)
Scan the confirmed requirements for nouns: organizer, meeting request, time slot, meeting, room, equipment, calendar, selection rule, and scheduler. These are candidates, not automatic classes. Ask what each one must remember or decide.
Final model
Types we will create
- Class
- Owns changing state or coordinates rules.
- Record
- A Java record for validated, immutable data with value equality.
- Interface
- Defines behavior that another implementation can replace.
Validates boundaries and owns overlap reasoning.
Carries one organizer's scheduling intent.
Records one accepted room reservation.
Owns suitability and its ordered schedule.
Keeps deterministic room choice replaceable.
Coordinates selection, IDs, indexing, and cancellation.
Returns a named scheduling outcome.
Enums
Enums name a small, fixed set of valid choices or outcomes.
Names the room capabilities a request may require.
Explains the result of scheduling a meeting.
Explains the result of cancelling a meeting.
Fields
Fields are simple values stored inside another type; they do not need independent behavior in this version.
Identifies who may cancel an accepted meeting.
Defines the minimum room capacity required.
Identifies the room assigned to a meeting.
Finds an accepted meeting for later operations.
Supporting implementation
Keeps meetings ordered by start time for neighbour checks.
See one request move through the objects
Swipe to follow the flow →

Read the arrows from left to right. The Organizer submits a request to MeetingScheduler. The scheduler asks RoomSelectionStrategy to compare rooms. The strategy calls each Room, and Room uses its capacity, equipment, ordered meetings, and the requested TimeSlot. The chosen Room reserves the slot, which allows the scheduler to create a Meeting. If no Room qualifies, the same path returns a rejection and no Meeting is created.
The boundary matters: the strategy may choose a Room, but it cannot write into that Room's schedule. Room.reserve repeats the availability check and owns the state change.
3. Class design (10–15 minutes)
Move from the entities into methods using the same chain:
requirement → responsible object → state it remembers → method that changes or reads that state
Start at MeetingScheduler.schedule because that is the caller-visible action. Then follow its questions downward: which rooms qualify, and how does one Room know that its time is free?
One class at a time
Design the state owners
Start with the coordinator, then follow the request into the classes that protect changing state. Select a class to see only the fields and behavior needed for its responsibility.
Class 1 of 2
MeetingScheduler
Coordinates deterministic room selection, accepted meetings, IDs, and cancellation.
Requirement behind this class
Schedule the smallest qualified room, cancel only for the owner, and leave accepted state unchanged after rejection.
State it remembers
Private information required to perform this class's job.
Provides the immutable selection candidates.
Finds the assigned room during cancellation and reads.
Finds accepted meetings by ID.
Delegates replaceable qualification and comparison.
Allocates IDs only after a room can be reserved.
Behavior it owns
Public use cases and internal rules kept beside that state.
Selects a Room, constructs a Meeting, reserves it, then indexes success.
Checks existence and ownership before removing from Room and index.
Returns a safe ordered meeting view.
Keep duplicate rooms and rejected ID consumption out of accepted state.
- Invariant it protects
- The meeting index and each Room schedule contain the same accepted reservations.
- Works with
- Room, RoomSelectionStrategy, MeetingRequest, Meeting, and ScheduleResult.
- Does not own
- Interval overlap, room suitability, or direct TreeMap mutation.
- Principle demonstrated
- Validation before mutation keeps IDs, indexes, and room schedules consistent.
Supporting types
These types carry stable data or define replaceable behavior. They support the state owners without becoming another place to store live workflow state.
Carry validated interval, intent, accepted reservation, and outcome data.
TimeSlot owns half-open overlap reasoning while the other records remain stable boundary values.
Defines how qualified rooms are compared.
It reads Rooms and returns a choice; it cannot reserve or mutate a schedule.
Filters suitability and chooses capacity then room ID.
The comparator makes equal candidates deterministic without moving workflow into the Strategy.
The explorer shows that Room—not MeetingScheduler—owns interval and schedule state. The next sections explain the half-open interval and TreeMap neighbour rules that make Room's invariant efficient.
Scheduling concept: half-open intervals
Write a time slot as [start, end). The square bracket means the start belongs to the meeting. The round bracket means the end does not.
For a meeting [09:00, 10:00), the occupied times include 09:00 and 09:59, but not 10:00. Therefore [10:00, 10:30) can begin immediately.
Two slots overlap only when both statements are true:
first.start < second.end
second.start < first.end
Why use two comparisons? Consider [09:00, 10:00) and [10:00, 10:30):
09:00 < 10:30is true.10:00 < 10:00is false.- Both are not true, so the slots do not overlap.
Using <= would wrongly reject every back-to-back meeting. That one character is a common interview bug.
- 1Focus contains [09:00, 10:00).
- 2A request asks for [10:00, 10:30).
- 3Existing start 09:00 is before requested end 10:30.
- 4Requested start 10:00 is not before existing end 10:00.
- 5The slots only touch, so Focus is available.
Why an ordered schedule needs only two neighbour checks
Room stores accepted meetings in a TreeMap keyed by start time. A TreeMap keeps keys sorted and can quickly find the closest entry before or after a requested start.
Suppose the schedule is:
[08:30, 09:00) [09:00, 10:00) [10:30, 11:00)
For a new [10:00, 10:30) slot:
floorEntry(10:00)returns[09:00, 10:00), the closest meeting starting at or before 10:00.- It touches but does not overlap.
ceilingEntry(10:00)returns[10:30, 11:00), the closest meeting starting at or after 10:00.- It also touches but does not overlap.
- No more distant meeting can overlap without crossing one of those neighbours.
This last step depends on an invariant: a promise the object always keeps. Room never stores overlapping meetings. Because existing entries do not overlap, checking the predecessor and successor is sufficient.
If one room has M meetings, each neighbour lookup costs O(log M). Checking R rooms costs O(R log M) in the simplified worst case, and inserting the accepted meeting costs another O(log M). Total space is O(R + N) for rooms and all N accepted meetings.
Understand deterministic room selection
The strategy filters in this order:
- Enough capacity.
- Every required equipment item.
- No overlapping meeting.
- Among survivors, lower capacity wins.
- If capacity ties, lower room ID wins.
For four people needing a projector, a free 4-seat room beats a free 10-seat room. This preserves the large room for a later request that may actually need it. The ID comparison is not a business optimization; it makes equal cases predictable.
RoomSelectionStrategy owns this comparison and nothing else. It may call Room.canHost, but it cannot reserve the winner, allocate an ID, or update scheduler indexes. That read-only boundary lets a future policy change without moving the Room invariant or cancellation workflow.
Decide whether patterns help
Use: Strategy for room selection
The requirement explicitly says selection may change. RoomSelectionStrategy.select(rooms, request) is the boundary. SmallestSuitableRoomStrategy implements today's capacity-and-ID rule. A future nearest-floor strategy can replace it without changing interval checks, meeting creation, or cancellation.
Do not force: Observer for invitations
The first version sends no invitations or notifications. There are no listeners to notify. Add Observer-like event handling only when a confirmed requirement introduces several reactions to a successful schedule or cancellation.
Do not force: Singleton MeetingScheduler
A global instance does not prevent conflicts across servers and makes tests share mutable state. Normal construction keeps rooms and strategy explicit.
Do not force: Factory for Meeting
Meeting construction is one visible record constructor after room selection. A factory would hide simple values without solving a creation rule. Add one only if creation later needs several meeting types or external configuration.
Prove the scheduling rule interactively
The default request starts exactly when Focus becomes free. Schedule it to prove the half-open boundary. Then load the overlap edge to see every room's rejection reason, or the capacity edge to see why Atlas is the only candidate.
Try the interval rule
Meeting room scheduler
4 · projector, whiteboard
6 · whiteboard
10 · projector, video, whiteboard
Selection order: suitable capacity and equipment → no overlap → smallest capacity → room ID.
New meeting request
What the scheduler decided
- Ready: try 10:00–10:30. Focus becomes free exactly at 10:00.
Connect the classes and review the final blueprint
MeetingScheduler has Rooms and one RoomSelectionStrategy. Each Room owns its ordered Meetings, and every Meeting contains one TimeSlot. These composition relationships match the responsibilities; none of the classes is a specialized form of another.
- Immutability: requests, time slots, and accepted meetings cannot change behind the schedule.
- Encapsulation: Room never exposes its mutable TreeMap and validates every insertion.
- Single responsibility: TimeSlot owns overlap, Room owns one schedule, Strategy selects, and Scheduler coordinates.
- Open/closed design: a new room policy implements the Strategy boundary without changing interval correctness.
- Validation before mutation: failed scheduling and cancellation leave schedules, indexes, and the next accepted ID unchanged.

How to read it: TimeSlot, request, meeting, and result records preserve requested and accepted facts.
Design principle: Immutability prevents accepted reservations changing later.
4. Java implementation (~10 minutes)
What the code does
This Java 17 program models room capacity and equipment, half-open time slots, ordered per-room schedules, deterministic smallest-room selection, accepted meetings, and owner-only cancellation.
Why it is structured this way
TimeSlot owns interval math. Room owns schedule correctness. Strategy compares qualified rooms. MeetingScheduler coordinates IDs and cancellation without reaching into a Room's private schedule.
Follow one request through the code
- 1MeetingScheduler receives one immutable MeetingRequest.
- 2SmallestSuitableRoomStrategy asks every Room whether it can host the request.
- 3Room checks capacity, equipment, and the two neighbouring schedule entries.
- 4The strategy selects the smallest capacity, then the lowest room ID.
- 5Room.reserve repeats validation and inserts the Meeting into its TreeMap.
- 6MeetingScheduler records the accepted Meeting by ID and returns it.
Code concepts to understand
Half-open TimeSlotOne value object keeps boundary behavior identical everywhere.
TimeSlot.overlaps uses two strict isBefore comparisons. Strict comparisons make equal end and start times non-overlapping. The constructor rejects an end that is equal to or before the start, so every created TimeSlot is meaningful.
TreeMap predecessor and successorAn ordered, non-overlapping schedule avoids scanning every meeting.
floorEntry(request.start) finds the closest existing start at or before the request. ceilingEntry(request.start) finds the closest at or after it. Room checks both with the same TimeSlot.overlaps method. This is correct because reserve never permits existing meetings to overlap.
Immutable request and result recordsRequested values and accepted facts cannot be edited behind the scheduler's back.
MeetingRequest, TimeSlot, and Meeting are records. Their constructors copy equipment sets, so a caller cannot modify the original Set after construction and silently change the request or meeting.
Validation before state changeA rejection leaves every room exactly as it was.
Strategy checks all qualifications before a Meeting exists. Room.reserve checks again before TreeMap.put. MeetingScheduler adds the global index only after reserve succeeds. Cancellation checks existence and ownership before removing from either place.
Strategy call flowThe pattern isolates only the rule that actually varies.
MeetingScheduler calls strategy.select(rooms, request). The strategy does not inspect private maps; it calls room.canHost(request). The returned Room still performs reserve. Selection and ownership of schedule state remain separate.
Start with the fixed values and interval object
Read TimeSlot first. Every later availability decision depends on its boundary rule. Then read the two immutable records that distinguish a requested meeting from an accepted one.
public enum Equipment {
PROJECTOR,
WHITEBOARD,
VIDEO
}import java.time.LocalDateTime;
public record TimeSlot(LocalDateTime start, LocalDateTime end) {
public TimeSlot {
if (start == null || end == null) {
throw new IllegalArgumentException("Start and end are required");
}
if (!start.isBefore(end)) {
throw new IllegalArgumentException("Start must be before end");
}
}
public boolean overlaps(TimeSlot other) {
if (other == null) {
throw new IllegalArgumentException("Other slot is required");
}
return start.isBefore(other.end)
&& other.start.isBefore(end);
}
}import java.util.Set;
public record MeetingRequest(
String title,
String organizerId,
TimeSlot slot,
int attendeeCount,
Set<Equipment> requiredEquipment
) {
public MeetingRequest {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("Title is required");
}
if (organizerId == null || organizerId.isBlank()) {
throw new IllegalArgumentException("Organizer is required");
}
if (slot == null || attendeeCount <= 0 || requiredEquipment == null) {
throw new IllegalArgumentException("Valid slot, attendees, and equipment are required");
}
requiredEquipment = Set.copyOf(requiredEquipment);
}
}import java.util.Set;
public record Meeting(
String id,
String title,
String organizerId,
String roomId,
TimeSlot slot,
int attendeeCount,
Set<Equipment> requiredEquipment
) {
public Meeting {
if (id == null || id.isBlank()
|| title == null || title.isBlank()
|| organizerId == null || organizerId.isBlank()
|| roomId == null || roomId.isBlank()) {
throw new IllegalArgumentException("Meeting identity fields are required");
}
if (slot == null || attendeeCount <= 0 || requiredEquipment == null) {
throw new IllegalArgumentException("Valid slot, attendees, and equipment are required");
}
requiredEquipment = Set.copyOf(requiredEquipment);
}
}Add clear result types
Expected scheduling and cancellation failures are normal outcomes, not programming exceptions. These enums let a caller distinguish “no room” from “wrong organizer” without parsing text.
public enum ScheduleStatus {
SCHEDULED,
NO_ROOM_AVAILABLE
}public enum CancellationStatus {
CANCELED,
MEETING_NOT_FOUND,
NOT_ORGANIZER
}import java.util.Optional;
public record ScheduleResult(
ScheduleStatus status,
Optional<Meeting> meeting
) {
public ScheduleResult {
if (status == null || meeting == null) {
throw new IllegalArgumentException("Result fields are required");
}
}
public static ScheduleResult accepted(Meeting meeting) {
return new ScheduleResult(
ScheduleStatus.SCHEDULED,
Optional.of(meeting)
);
}
public static ScheduleResult rejected() {
return new ScheduleResult(
ScheduleStatus.NO_ROOM_AVAILABLE,
Optional.empty()
);
}
}Let Room protect the ordered schedule
Read isAvailable before reserve. The first method explains the neighbour algorithm; the second shows how the state change remains behind the same validation boundary.
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public final class Room {
private final String id;
private final String name;
private final int capacity;
private final Set<Equipment> equipment;
private final TreeMap<LocalDateTime, Meeting> schedule = new TreeMap<>();
public Room(String id, String name, int capacity, Set<Equipment> equipment) {
if (id == null || id.isBlank() || name == null || name.isBlank()) {
throw new IllegalArgumentException("Room id and name are required");
}
if (capacity <= 0 || equipment == null) {
throw new IllegalArgumentException("Valid capacity and equipment are required");
}
this.id = id;
this.name = name;
this.capacity = capacity;
this.equipment = Set.copyOf(equipment);
}
public String id() {
return id;
}
public String name() {
return name;
}
public int capacity() {
return capacity;
}
public Set<Equipment> equipment() {
return equipment;
}
public boolean canHost(MeetingRequest request) {
return capacity >= request.attendeeCount()
&& equipment.containsAll(request.requiredEquipment())
&& isAvailable(request.slot());
}
public boolean isAvailable(TimeSlot requested) {
Map.Entry<LocalDateTime, Meeting> previous =
schedule.floorEntry(requested.start());
if (previous != null && previous.getValue().slot().overlaps(requested)) {
return false;
}
Map.Entry<LocalDateTime, Meeting> next =
schedule.ceilingEntry(requested.start());
return next == null || !next.getValue().slot().overlaps(requested);
}
public boolean reserve(Meeting meeting) {
if (!id.equals(meeting.roomId())
|| capacity < meeting.attendeeCount()
|| !equipment.containsAll(meeting.requiredEquipment())
|| !isAvailable(meeting.slot())) {
return false;
}
schedule.put(meeting.slot().start(), meeting);
return true;
}
public boolean remove(Meeting meeting) {
if (meeting == null || !id.equals(meeting.roomId())) {
return false;
}
return schedule.remove(meeting.slot().start(), meeting);
}
public List<Meeting> meetings() {
return List.copyOf(schedule.values());
}
}Isolate the selection rule
The interface is deliberately small. Its implementation filters by Room.canHost and uses min, so it does not need to sort every qualified room.
import java.util.List;
import java.util.Optional;
public interface RoomSelectionStrategy {
Optional<Room> select(List<Room> rooms, MeetingRequest request);
}import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public final class SmallestSuitableRoomStrategy
implements RoomSelectionStrategy {
@Override
public Optional<Room> select(
List<Room> rooms,
MeetingRequest request
) {
return rooms.stream()
.filter(room -> room.canHost(request))
.min(Comparator.comparingInt(Room::capacity)
.thenComparing(Room::id));
}
}Finish with the coordinator and demo
MeetingScheduler.schedule orders the complete workflow. cancel deliberately checks everything before the first removal, so an unknown ID or wrong organizer changes neither the Room nor the global index.
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public final class MeetingScheduler {
private final List<Room> rooms;
private final Map<String, Room> roomsById;
private final Map<String, Meeting> meetingsById = new LinkedHashMap<>();
private final RoomSelectionStrategy strategy;
private int nextMeetingNumber = 1;
public MeetingScheduler(
List<Room> rooms,
RoomSelectionStrategy strategy
) {
if (rooms == null || rooms.isEmpty() || strategy == null) {
throw new IllegalArgumentException("Rooms and strategy are required");
}
this.rooms = List.copyOf(rooms);
this.strategy = strategy;
this.roomsById = new LinkedHashMap<>();
for (Room room : rooms) {
if (roomsById.putIfAbsent(room.id(), room) != null) {
throw new IllegalArgumentException("Duplicate room: " + room.id());
}
}
}
public ScheduleResult schedule(MeetingRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request is required");
}
Optional<Room> selected = strategy.select(rooms, request);
if (selected.isEmpty()) {
return ScheduleResult.rejected();
}
Room room = selected.get();
String meetingId = "M" + nextMeetingNumber;
Meeting meeting = new Meeting(
meetingId,
request.title(),
request.organizerId(),
room.id(),
request.slot(),
request.attendeeCount(),
request.requiredEquipment()
);
if (!room.reserve(meeting)) {
return ScheduleResult.rejected();
}
nextMeetingNumber++;
meetingsById.put(meeting.id(), meeting);
return ScheduleResult.accepted(meeting);
}
public CancellationStatus cancel(
String meetingId,
String organizerId
) {
Meeting meeting = meetingsById.get(meetingId);
if (meeting == null) {
return CancellationStatus.MEETING_NOT_FOUND;
}
if (!meeting.organizerId().equals(organizerId)) {
return CancellationStatus.NOT_ORGANIZER;
}
Room room = roomsById.get(meeting.roomId());
if (!room.remove(meeting)) {
throw new IllegalStateException("Meeting index and room disagree");
}
meetingsById.remove(meetingId);
return CancellationStatus.CANCELED;
}
public List<Meeting> meetingsForRoom(String roomId) {
Room room = roomsById.get(roomId);
return room == null ? List.of() : room.meetings();
}
}import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Set;
public final class MeetingRoomSchedulerDemo {
public static void main(String[] args) {
LocalDate day = LocalDate.of(2026, 8, 14);
MeetingScheduler scheduler = sampleScheduler();
TimeSlot firstSlot = slot(day, 9, 0, 10, 0);
TimeSlot touchingSlot = slot(day, 10, 0, 10, 30);
System.out.println("back-to-back overlap: "
+ firstSlot.overlaps(touchingSlot));
ScheduleResult first = scheduler.schedule(request(
"Daily sync", "organizer-a", firstSlot, 4
));
System.out.println("first room: "
+ first.meeting().orElseThrow().roomId());
ScheduleResult touching = scheduler.schedule(request(
"Design review", "organizer-b", touchingSlot, 4
));
System.out.println("back-to-back room: "
+ touching.meeting().orElseThrow().roomId());
ScheduleResult fallback = scheduler.schedule(request(
"Interview", "organizer-c", slot(day, 9, 30, 10, 0), 4
));
System.out.println("conflict fallback room: "
+ fallback.meeting().orElseThrow().roomId());
ScheduleResult rejected = scheduler.schedule(request(
"Urgent review", "organizer-d", slot(day, 9, 45, 10, 0), 4
));
System.out.println("fully occupied: " + rejected.status());
String firstId = first.meeting().orElseThrow().id();
System.out.println("wrong cancellation: "
+ scheduler.cancel(firstId, "organizer-b"));
System.out.println("owner cancellation: "
+ scheduler.cancel(firstId, "organizer-a"));
ScheduleResult reopened = scheduler.schedule(request(
"Replacement", "organizer-e", slot(day, 9, 0, 9, 30), 4
));
System.out.println("reopened room: "
+ reopened.meeting().orElseThrow().roomId());
}
private static MeetingScheduler sampleScheduler() {
Room focus = new Room(
"R1", "Focus", 4,
Set.of(Equipment.PROJECTOR, Equipment.WHITEBOARD)
);
Room cedar = new Room(
"R2", "Cedar", 6,
Set.of(Equipment.WHITEBOARD)
);
Room atlas = new Room(
"R3", "Atlas", 10,
Set.of(
Equipment.PROJECTOR,
Equipment.WHITEBOARD,
Equipment.VIDEO
)
);
return new MeetingScheduler(
List.of(focus, cedar, atlas),
new SmallestSuitableRoomStrategy()
);
}
private static MeetingRequest request(
String title,
String organizer,
TimeSlot slot,
int attendees
) {
return new MeetingRequest(
title,
organizer,
slot,
attendees,
Set.of(Equipment.PROJECTOR)
);
}
private static TimeSlot slot(
LocalDate day,
int startHour,
int startMinute,
int endHour,
int endMinute
) {
return new TimeSlot(
LocalDateTime.of(day, LocalTime.of(startHour, startMinute)),
LocalDateTime.of(day, LocalTime.of(endHour, endMinute))
);
}
}Trace the compiled behavior
- 1The first [09:00, 10:00) request needs four seats and a projector. Focus is the smallest qualified room, so M1 is inserted there.
- 2The [10:00, 10:30) request touches M1's end but does not overlap. Focus is selected again.
- 3A [09:30, 10:00) request conflicts in Focus. Cedar lacks a projector, so Atlas becomes the valid fallback.
- 4A [09:45, 10:00) request now conflicts in Focus and Atlas while Cedar is unsuitable. It returns NO_ROOM_AVAILABLE and creates no ID.
- 5A different organizer tries to cancel M1. The scheduler returns NOT_ORGANIZER and both indexes stay unchanged.
- 6The real organizer cancels M1. Room.remove frees [09:00, 10:00), and a replacement request chooses Focus again.
5. Extensions (~5 minutes)
Each follow-up begins by changing one requirement, then changing the smallest responsible boundary.
Add room setup and cleanup buffers
If every meeting needs ten minutes of preparation, the requested interval used for conflict checking expands before selection. TimeSlot can expose a buffered copy, while the Meeting still stores the visible start and end. Clarify whether back-to-back meetings are still allowed before changing the formula.
Add recurring meetings
A recurring request is not one Meeting. Add a recurrence rule that expands into a finite set of TimeSlots for a stated date range. Scheduling must validate all occurrences before reserving any, or roll back earlier inserts if a later date fails. Do not store “forever” recurrence without a boundary.
Add attendee calendars
The new requirement creates an Attendee calendar owner. Room availability and person availability become separate checks. MeetingScheduler coordinates both, but each calendar should still own its own overlap rule. Invitations and response states enter only if the interviewer asks for them.
Handle simultaneous requests
The current check-then-reserve flow assumes sequential calls. In one Java process, Room.reserve could protect its neighbour check and insert with a lock. Across application servers, use a shared database transaction or constraint. A Strategy result alone is never a distributed reservation guarantee.
Search a large organization efficiently
With thousands of rooms, first index fixed properties such as building, capacity, and equipment. The scheduler retrieves a smaller candidate set, then checks each candidate's ordered schedule. Keep the exact capacity and room-ID tie-breakers so the result remains deterministic.