Last Updated: April 3, 2026
We need to build a calendar system that accepts or rejects event bookings. Each event occupies a half-open interval [start, end), meaning the event includes the start time but excludes the end time. This is the standard convention for time intervals because it makes adjacent events seamless. An event ending at 20 and another starting at 20 do not overlap.
The core challenge is detecting overlaps. Two intervals [s1, e1) and [s2, e2) overlap if and only if s1 < e2 AND s2 < e1. If either condition fails, the intervals are disjoint. This overlap condition is the foundation for every approach we'll discuss.
So the problem reduces to: maintain a collection of non-overlapping intervals. For each new interval, check if it conflicts with any existing one. If not, add it. If it does, reject it.
0 <= start < end <= 10^9 -> The interval endpoints can be huge, so we can't allocate an array indexed by time. We need to store intervals explicitly.1000 calls to book -> With n up to 1000, even an O(n) per call solution (O(n^2) total) is fine. But an O(log n) approach is more interesting and worth knowing for follow-ups.The most natural approach: keep a list of all booked events. When a new booking comes in, check it against every existing event for overlaps. If none overlap, add the new event to the list.
Two intervals [s1, e1) and [s2, e2) overlap when s1 < e2 AND s2 < e1. Think of it this way: the intervals are disjoint only if one ends before the other starts. So they overlap unless e1 <= s2 (first ends before second starts) or e2 <= s1 (second ends before first starts). Flipping that gives us the overlap condition.
(start, end) pairs representing booked events.book(start, end) call, iterate through all existing events.(s, e), check if start < e AND s < end. If both are true, there's an overlap, so return false.(start, end) to the list and return true.book call, O(n^2) total for n calls. Each new booking checks against all existing bookings. Total comparisons: 0 + 1 + 2 + ... + (n-1) = O(n^2).For each new booking, we scan through every existing event. Most of those comparisons are wasted since most events are nowhere near the new one. What if we kept the events sorted so we could jump directly to the ones that might actually overlap?
Here's the key insight: if we keep our bookings sorted by start time, we don't need to check every single one. A new interval [start, end) can only overlap with its immediate neighbors in the sorted order. Specifically, we need to find where the new interval would be inserted in the sorted list and check the intervals just before and just after that position.
Why only the neighbors? Because the intervals are non-overlapping and sorted. If the new interval doesn't overlap with its immediate predecessor and doesn't overlap with its immediate successor, then it can't overlap with anything else either. Everything to the left ends even earlier, and everything to the right starts even later.
(start, end) pairs, sorted by start time.book(start, end), use binary search to find the index where start would be inserted.prev.end > start, there's an overlap.end > next.start, there's an overlap.true.book call (O(log n) search + O(n) insertion into array). The binary search is O(log n), but inserting into the middle of an ArrayList/vector requires shifting elements, which is O(n).The binary search finds neighbors in O(log n), but the array insertion still costs O(n) due to shifting. What if we used a data structure that supports both search and insertion in O(log n)?
A balanced binary search tree (like Java's TreeMap, C++'s map, or Python's SortedList) gives us both O(log n) search and O(log n) insertion. This is the natural evolution from Approach 2: keep the same sorted-order neighbor-checking logic but switch to a data structure that doesn't need O(n) shifts.
In a TreeMap, we can use floorEntry to find the closest event starting at or before the new start, and ceilingEntry to find the closest event starting at or after the new start. Check both neighbors for overlap, and if there's no conflict, insert the new event.
The TreeMap maintains all intervals in sorted order by start time, with O(log n) lookups and insertions. The floorEntry/ceilingEntry operations find the two neighbors in O(log n), and the insertion is also O(log n) because balanced BSTs don't need to shift elements like arrays do.
The correctness argument is the same as Approach 2: since all stored intervals are non-overlapping and sorted, only the immediate neighbors in the sorted order can conflict with a new interval. If the new interval fits in the gap between its neighbors, it's safe to add.
For languages without a built-in balanced BST with floor/ceiling operations (Go, JavaScript, TypeScript), we fall back to the sorted array approach. The search is O(log n) but insertion remains O(n) due to array shifting.
book(start, end):start.start.start, overlap detected.end > its start time, overlap detected.(start, end) into the TreeMap and return true.book call (for Java, C++, Python with SortedList). Both the neighbor lookup and the insertion are O(log n) in a balanced BST. For languages using sorted arrays (Go, JS, TS), insertion is O(n) due to shifting.