Code a meeting room booking system
Open in the interactive app: practice, quiz, mock interview →In one minute
- A meeting scheduler answers one question: "when is the earliest time that all these people and a big enough room are free?", and then books that answer before anyone else takes it. Search and claim must be one atomic step, or two organizers both get the 10:00 slot in the same room.
- Everything is an interval. Model time as half-open ranges
[start, end)on one UTC timeline, so a meeting ending at 10:00 and one starting at 10:00 do not collide, and convert to local time only at the edges. - Rooms are hard constraints; people are soft ones. A room cannot hold two meetings, but a person can be double-booked if they choose. The scheduler searches with both, and enforces only the room.
- The earliest feasible start is either the earliest allowed time or the end of some existing busy interval. That observation turns an open-ended search into a scan over a small candidate set.
- The shape is contention plus finding things fast: the same interval logic schedules surgeons and operating theatres, CI jobs on build agents, and maintenance windows across services that must all be quiet.
Requirements
Functional:
- Rooms have a name and a capacity. Users have calendars made of meetings and personal blocks (focus time, out of office).
- An organizer asks for a meeting with a list of attendees, a duration and a search window. The system finds the earliest start where everyone is free and a room with enough seats is free, picks the smallest such room (ties by name), and books it.
- The organizer can cancel. An attendee can decline, which frees their time but keeps the meeting.
- Each user can view their agenda in time order, with rooms.
- Attendees are notified of invitations, changes and cancellations.
Non-functional:
- A room is never booked twice for overlapping times, even with concurrent organizers.
- Back-to-back meetings are allowed:
[9:00, 10:00)and[10:00, 11:00)do not overlap. - Scheduling a meeting for 10 people over a week of calendars should answer in well under 100 ms.
Out of scope: recurring meetings (RRULE expansion), time-zone preferences per attendee, video conferencing links and external invitations over iCalendar. The Design Calendar System lesson covers recurrence and free/busy at scale; the Machine Coding Approach lesson builds a command-driven room booking interpreter, so this lesson focuses on the object model and the joint search across people and rooms.
Entities and relationships
- Interval is a value
(start, end)in UTC minutes or seconds withstart < endand one method that matters:overlaps(other), which isa.start < b.end && b.start < a.end. - Room has a name, a capacity and equipment flags. Its schedule is a sorted list of bookings.
- User has an id and a time zone, and owns a Calendar: a sorted collection of busy intervals, each pointing at a meeting or a personal block.
- Meeting has an id, an organizer, attendees with an RSVP status (invited, accepted, declined), an interval and a room. The organizer is a special attendee who cannot decline.
- Scheduler is the façade: it owns rooms, calendars and meetings, and runs search plus booking under one lock.
- SlotFinder and RoomSelector are strategies: how to search for time (earliest, least disruption, preferred hours) and how to choose among free rooms (smallest fit, nearest floor, has video).
- Notifier sends invitations and updates, subscribed to meeting events.
Relationships: the scheduler is composed of rooms and meetings. A meeting references one room and several users. Calendars index meetings by participant, and room schedules index them by room; both are derived views of the same meeting records and must be updated together.
Class diagram
classDiagram
class Scheduler {
-rooms: Map~RoomName, Room~
-calendars: Map~UserId, Calendar~
-meetings: Map~MeetingId, Meeting~
+schedule(organizer, attendees, duration, window) Meeting
+cancel(meetingId, user)
+decline(meetingId, user)
+block(user, interval)
+agenda(user) Entry[]
}
class Interval {
+start: int
+end: int
+overlaps(other) bool
}
class Room {
+name: string
+capacity: int
-bookings: Interval[]
}
class Calendar {
-busy: Interval[]
+isFree(interval) bool
}
class Meeting {
+id: string
+organizer: UserId
+attendees: Map~UserId, Rsvp~
+interval: Interval
+room: RoomName
}
class SlotFinder {
<<interface>>
+find(calendars, rooms, duration, window) Slot
}
class RoomSelector {
<<interface>>
+choose(freeRooms, headcount) Room
}
class Notifier {
<<interface>>
+onEvent(event)
}
Scheduler "1" *-- "many" Room
Scheduler "1" *-- "many" Calendar
Scheduler "1" *-- "many" Meeting
Meeting --> Room
Meeting --> Interval
Scheduler --> SlotFinder
Scheduler --> RoomSelector
Scheduler --> NotifierKey flows
The search reads many calendars, but the commit only needs the room to be exclusive. Holding one lock across both keeps the answer valid at the moment it is booked.
sequenceDiagram
participant O as Organizer
participant S as Scheduler
participant F as SlotFinder
participant R as RoomSelector
participant N as Notifier
O->>S: schedule(ann, [bob, cy], 60, Mon 9:00 to 17:00)
S->>S: lock
S->>F: candidates: 9:00 plus every busy end
F-->>S: 10:00 (bob busy until 10:00)
S->>R: rooms free 10:00 to 11:00, 3 seats
R-->>S: Bonsai (4 seats)
S->>S: book room and calendars, unlock
S-->>O: Meeting M12, Bonsai, 10:00
S->>N: MeetingCreated(M12)
N-->>O: invitations sent to bob and cyDesign patterns used and why
- Strategy for slot finding and room selection. "Earliest slot" is the interview default, but real products also offer "fewest conflicts", "within everyone's working hours" and "prefer rooms on my floor". Each is a class; the scheduler does not change.
- Observer for notifications and integrations. Invitation emails, room display panels and chat reminders subscribe to
MeetingCreated,MeetingCancelledandAttendeeDeclinedevents. Fire them after the lock is released, so a slow mail server cannot stall booking. - Value object for
Interval. All overlap logic lives in one tested method, which kills an entire class of off-by-one bugs at the boundaries. - Façade: the scheduler is the only way to mutate rooms and calendars, which is what keeps the two indexes consistent.
- Command (optional) for changes such as reschedule and cancel, if you need an audit trail or undo in the UI. Each command records who did what, which helps when someone asks "who moved my meeting?".
Concurrency and edge cases
- Two organizers, one room. Both search, both see Bonsai free at 10:00, both book. The search and the booking must happen under one lock in memory, or the booking must be a conditional insert that the database rejects on overlap. Postgres can enforce this with an exclusion constraint:
EXCLUDE USING gist (room WITH =, during WITH &&)on atstzrangecolumn, which turns a race into a clean constraint violation that the losing request retries. - People are not locked. Locking every attendee's calendar while searching would serialise half the company behind one all-hands. Treat people's free/busy as advisory: search with it, but allow a person to end up double-booked if two meetings land at once, and let them decline one.
- Boundaries. Use half-open intervals everywhere. With closed intervals, a meeting ending at 10:00 blocks one starting at 10:00, and the bug appears only for back-to-back bookings.
- Time zones and daylight saving. Store UTC. Search windows given in local time ("9 to 5 for everyone") must be converted per attendee with a real tz database, because 9:00 in London and 9:00 in New York are different instants, and the offset changes on different dates in the two cities.
- Organizer leaves. If the organizer's account is deactivated, either transfer ownership or cancel and notify. Do not leave orphaned room bookings that nobody can cancel.
- Ghost meetings. Rooms booked and never used waste space. A check-in requirement (release the room if nobody checks in within 10 minutes) is a scheduled job with an injected clock, built on the same interval model.
Down the stack
Layer 1The contract
schedule returns a meeting whose interval lies inside the search window, during which every participant had no busy interval and the chosen room had no booking, and no earlier start satisfies the same conditions; otherwise it returns nothing and changes nothing. After it returns, no other meeting overlaps it in the same room. cancel by the organizer removes the meeting from the room and every calendar; decline removes only that attendee's busy interval.
Layer 2The mechanism
The search uses an event-point argument: if a start s is feasible and s is not the window start, sliding it earlier eventually hits either the window start or the end of some busy interval, so the earliest feasible start is in the set made of the window start plus every busy end inside the window. With k busy intervals across participants and rooms, that is at most k + 1 candidates. Each candidate is checked against each calendar with a binary search in a sorted list (find the last interval starting before the candidate's end, check whether it ends after the candidate's start), so the cost is O(k × p × log n) for p participants. An alternative is to merge all participants' busy lists into one sorted union (a k-way merge with a heap) and walk the gaps, which is what free/busy APIs such as Google Calendar's freebusy.query and Microsoft Graph's findMeetingTimes expose to clients. For a room's own schedule, an interval tree or a balanced tree keyed by start gives O(log n) overlap checks and inserts.
Layer 3The machine
A week of a busy person's calendar is about 40 intervals; at 16 bytes per interval (two 64-bit integers) that is 640 bytes, ten cache lines. Checking 10 people and 20 rooms against 200 candidate starts is about 6,000 binary searches of six or seven comparisons each, a few tens of microseconds on one core. The slow parts are elsewhere: loading calendars from a database (one indexed range query per participant, each around 1 ms), and in a distributed product, fetching free/busy from another organization's server over HTTPS (50 to 300 ms). That is why real schedulers batch free/busy reads, cache them for a short time and re-check only the room at commit.
Why it's built this way
- Time slots are continuous but bookings are few, so candidate starts come from busy-interval ends, not from scanning every minute.
- Rooms are physical and exclusive, so the room booking needs a serialization point: a lock or an exclusion constraint.
- People are many and their calendars are read far more than written, so their free/busy is read without locks and treated as advice.
- Boundaries and time zones are where calendar bugs live, so time is one UTC timeline of half-open intervals and conversion happens once, at the edges.
Extensibility
- Recurring meetings. Store a rule (RRULE) on a series and expand occurrences within a horizon when searching. Room booking becomes "every occurrence must be free", with per-occurrence exceptions.
- Working hours and preferences. A new
SlotFinderintersects the search window with each attendee's working hours in their own time zone before scanning candidates. - Optional attendees. Search first with required attendees only, then rank candidates by how many optional attendees are free. That is a scoring strategy, not a change to the scheduler.
- Room equipment. Add a specification filter (video, whiteboard, wheelchair access) to
RoomSelector. Capacity is just one predicate among several. - Multiple buildings. Partition rooms by building; a meeting books one room per building for hybrid teams, all or nothing, using the same atomic multi-room claim as a multi-line checkout.
Leadership lens
- Build first: the
Intervaltype with exhaustive boundary tests and the atomic room claim. Those two prevent the bugs that make people distrust the tool, and distrust is what kills internal scheduling products. - Decide explicitly what is hard and what is soft. If product insists that people can never be double-booked, the locking scope grows to every attendee, and you should show the cost before agreeing.
- Ask in review: which time zone defines "9 to 5"; what happens when two people book the last room at the same instant; how does a cancellation reach attendees' phones; what releases unused rooms.
- Integration with an external calendar (Google Workspace, Microsoft 365) usually decides the architecture. Find out early whether you are the system of record or a layer on top of theirs.
Check yourself
Why are half-open intervals the default for meetings?
Show answer
They make back-to-back meetings not overlap, and the overlap test
a.start < b.end && b.start < a.endis correct without special cases. Closed intervals make a 10:00 end collide with a 10:00 start.Why is the earliest feasible start always the window start or the end of some busy interval?
Show answer
If a feasible start is neither, you can slide it earlier without creating a conflict until it hits the window start or a busy end. So checking those candidates is enough to find the earliest.
Why lock the room but not the attendees' calendars?
Show answer
A room cannot hold two meetings, so it needs exclusivity. People can resolve their own conflicts by declining, and locking every attendee would serialise large meetings and hurt throughput for little gain.
Two organizers race for the same room at 10:00. How does a database-backed design guarantee only one wins?
Show answer
An exclusion constraint on room and time range, or a conditional insert inside a transaction, rejects the second overlapping row. The losing request sees the error and searches again.
Watch
- Design a Meeting Scheduler: Class Diagram, Design Patterns, Concurrency and Code (Devaraj Umapathi): an end-to-end low-level design with a concurrency section.
- Salesforce machine coding round: Meeting Room Reservation System (Code design by Naman): a coded solution focused on preventing overlap and double booking.
- Mock Low Level System Design Interview: Design Meeting Scheduler (Concept && Coding): a mock interview with interviewer probes on the same problem.
Free resources
- Range types and exclusion constraints (PostgreSQL docs):
tstzrange, the&&overlap operator andEXCLUDE USING gistfor rooms that cannot double-book. - Freebusy: query (Google Calendar API): how a production calendar exposes busy intervals for a set of people.
- findMeetingTimes (Microsoft Graph): a real API that suggests slots from attendee and room availability.
- RFC 5545: iCalendar: the standard for events, free/busy and recurrence rules.
- Interval tree (Wikipedia): the data structure for fast overlap queries on a room's bookings.