Engineering Atlas › Low-Level Design

Code a meeting room booking system

12 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

Requirements

Functional:

Non-functional:

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

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 --> Notifier

Key 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 cy

Design patterns used and why

Concurrency and edge cases

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

  1. Time slots are continuous but bookings are few, so candidate starts come from busy-interval ends, not from scanning every minute.
  2. Rooms are physical and exclusive, so the room booking needs a serialization point: a lock or an exclusion constraint.
  3. People are many and their calendars are read far more than written, so their free/busy is read without locks and treated as advice.
  4. 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

Leadership lens

Check yourself

  1. 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.end is correct without special cases. Closed intervals make a 10:00 end collide with a 10:00 start.

  2. 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.

  3. 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.

  4. 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

Free resources

Open in the interactive app: practice, quiz, mock interview →