Meeting room capacity: count simultaneous demandLESSON 2.11 · 11 OF 43 IN CHAPTER
PART A / Coding problems and trade-offs
Step 37 of 252
LESSON 2.11 · 11 OF 43 IN CHAPTERTry it, then open the solution

Meeting room capacity: count simultaneous demand

Constructed practice problem; no company attribution. Prerequisites: interval endpoints.

Candidate brief

THE PROBLEM

An office scheduler needs the minimum number of interchangeable rooms for a batch of meetings. A room can be reused exactly when its previous meeting ends. Return capacity, not assignments. Why does merging busy intervals lose the quantity we need?

Write this:

def meeting_room_capacity(intervals):
    ...
Contract Decision
Input List/tuple of integer (start, end) pairs with start < end
Output Nonnegative integer: maximum simultaneous half-open meetings
Boundaries Empty gives 0; duplicate meetings count separately; touching meetings share a room
Invalid input Invalid container/pair/type or nonpositive duration raises ValueError
Excluded Room features, travel buffers, recurring meetings, or actual room IDs
Optional refresher · the underlying tool

A meeting interval [start,end) occupies its start and frees the room at its end. So [9,10) and [10,11) need one room; sharing a boundary is not overlap.

meetings = [(9,10), (10,11)]
print(meetings[0][1] <= meetings[1][0])  # True: reuse is legal

State the endpoint policy before sorting arrivals and departures. The maximum simultaneous occupancy sets the required room count.

A design choice worth saying aloud

At equal timestamps, end frees a room before start occupies it. At time 10, release a [9,10) room before admitting [10,11); reversing the tie order inflates capacity. State whether the output is the peak simultaneous occupancy or an actual assignment of room IDs; the latter needs more state.

What the interviewer expects

The interviewer gives you the scenario and the contract above. Explain what a successful call returns, walk through one example below, and name what your state means before choosing a data structure.

Done means: Nonnegative integer: maximum simultaneous half-open meetings.

Now predict each output before looking at the reference; invalid input should leave any existing state unchanged unless the contract says otherwise.

Test-case scenarios to settle before coding

01 · Representative

Input / starting state
[(0,10),(5,7),(7,12)]
Expected result
2

What it is testing: End-before-start tie handling lets a room turn over at 7.

02 · Empty

Input / starting state
[]
Expected result
0

What it is testing: No rooms are required.

03 · Touching

Input / starting state
[(1,2),(2,3)]
Expected result
1

What it is testing: Half-open meetings share a room.

04 · Duplicates

Input / starting state
[(1,4),(1,4)]
Expected result
2

What it is testing: Multiplicity matters even for identical intervals.

05 · Nested

Input / starting state
[(0,10),(2,3),(4,5)]
Expected result
2

What it is testing: Peak concurrency is not number of meetings.

06 · Invalid/atomic

Input / starting state
[(4,4)]
Expected result
ValueError; input unchanged

What it is testing: Zero-duration entries are outside the contract.

For each case, show which branch or state change produces that result.

meeting_room_capacity([(0, 10), (5, 7), (7, 12)]) == 2. At time 7 one meeting ends and another starts, so demand stays 2. meeting_room_capacity([(1, 2), (2, 3)]) == 1; [(4, 4)] raises ValueError.

Before opening the explanation, restate the contract, trace the smallest useful example, implement a baseline, and identify the repeated work. Then implement your improvement independently and derive tests from the contract. Say what your state means before saying which data structure stores it.

Worked lesson, changed requirements, and reference

Keep multiplicity instead of only union coverage

A baseline checks every meeting start and counts intervals containing that time: start <= time < end. Demand can increase only at a start, so the maximum of those counts is correct. That costs O(n²) time and constant extra state. Merging intervals is not an improvement for this output: two identical meetings merge to one block but still require two rooms. The representation must preserve how many intervals are active.

Represent each meeting by two events, (start, +1) and (end, -1), then sort by time and delta. The delta tie-break processes departures before arrivals.

Time Event Active after event Maximum so far
0 start [0, 10) 1 1
5 start [5, 7) 2 2
7 end [5, 7) 1 2
7 start [7, 12) 2 2
10 end [0, 10) 1 2
12 end [7, 12) 0 2

After each event, active is the number of starts processed minus ends processed. At a shared timestamp, intermediate departure states may be below the eventual demand, but processing departures first cannot create a false peak. The maximum after all arrivals at each timestamp is exactly simultaneous demand under the half-open contract. Any schedule needs at least that many rooms. Conversely, when a new meeting begins and fewer than that many rooms are occupied, one room is free, so that bound suffices for interchangeable rooms.

Validation and event creation take O(n), sorting O(n log n), and scanning O(n). Auxiliary space is O(n) for events and Python sorting workspace; output is one integer. Explain this lower-bound-and-construction argument rather than simply asserting that a peak is “obviously” the answer.

Follow-up 1: return actual room assignments

Predict what the count discarded: identities and availability times. Sort meetings by start with original indices, use a min-heap of busy (end, room_id) entries, and release every room whose end is at most the next start.

Next meeting Rooms released first Assigned room Busy afterward
[0, 10) none 0 (10, 0)
[5, 7) none 1 (7, 1), (10, 0)
[7, 12) room 1 1 (10, 0), (12, 1)

A second heap of free room IDs makes a smallest-ID tie rule deterministic. Keep output in original input order. Assignment needs O(n) output, while the busy/free heaps need O(capacity) state beyond sorting.

Follow-up 2: each room needs a cleanup buffer

Diagram: Follow-up 2: each room needs a cleanup buffer

Extend occupancy end times by the agreed nonnegative buffer before sorting; do not merely change < to <=, which models endpoint inclusion rather than elapsed cleanup time. A senior candidate tests simultaneous starts/ends and duplicate intervals. A lead candidate clarifies whether room features and room-specific buffers invalidate interchangeability; capacity alone may then be insufficient to prove that a feasible assignment exists.

Run and check

From the repository root:

cd curriculum/01-code/02-data-structures-algorithms/problems/10-meeting-room-capacity
python -m unittest -v test_solution.py

Reference implementation (download file, source below) · Contract and oracle tests (download file, source below). Read the tests after your attempt. A green reference suite verifies the supplied implementation; it does not demonstrate independent transfer. Reimplement one follow-up with the reference closed and explain which old invariant no longer holds.

Reference implementation · solution.py
def _intervals(intervals):
    if not isinstance(intervals, (list, tuple)):
        raise ValueError("expected a list or tuple of intervals")
    for pair in intervals:
        if (not isinstance(pair, (list, tuple)) or len(pair) != 2
                or any(type(x) is not int for x in pair) or pair[0] >= pair[1]):
            raise ValueError("intervals require integer start < end")

def meeting_room_capacity(intervals):
    _intervals(intervals)
    events = []
    for start, end in intervals:
        events.append((start, 1))
        events.append((end, -1))
    active = best = 0
    for _, delta in sorted(events):
        active += delta
        best = max(best, active)
    return best
Contract and oracle tests · test_solution.py
import random
import unittest
from solution import meeting_room_capacity

class RoomTests(unittest.TestCase):
    def test_endpoints_and_multiplicity(self):
        self.assertEqual(meeting_room_capacity([(0, 10), (5, 7), (7, 12)]), 2)
        self.assertEqual(meeting_room_capacity([(1, 2), (2, 3)]), 1)
        self.assertEqual(meeting_room_capacity([(1, 2)] * 4), 4)
        self.assertEqual(meeting_room_capacity([]), 0)
        for intervals in [[(4, 4)], [(4, 3)], [(1, False)], ["ab"], None]:
            with self.assertRaises(ValueError):
                meeting_room_capacity(intervals)

    def test_active_at_starts_oracle(self):
        rng = random.Random(110)
        for _ in range(500):
            intervals = []
            for _ in range(rng.randrange(20)):
                start = rng.randrange(-5, 10)
                intervals.append((start, start + rng.randrange(1, 8)))
            expected = max((sum(start <= time < end for start, end in intervals)
                            for time, _ in intervals), default=0)
            original = intervals[:]
            self.assertEqual(meeting_room_capacity(intervals), expected)
            self.assertEqual(intervals, original)

if __name__ == "__main__":
    unittest.main()