Intervals¶
At a glance
Priority: P0 · Complexity: ⅖ · Est. time: 2 h · Phase: 5 · Prereqs: Two pointers, Heap, Greedy You're done when: you can decide "sort by start vs end, merge vs sweep vs heap" for any interval problem in one minute, and write the overlap condition without off-by-one errors.
Why it matters¶
Interval problems are pervasive in scheduling, calendars, resource booking, time-series windows, IP-range allow lists, and price-validity periods. They test whether you sort by the right key and reason about overlap precisely. Most are Easy/Medium but candidates fail on the boundary conditions (touching intervals: [1,2] and [2,3]?), which is why you always clarify closed vs half-open ranges.
Core concepts¶
Pattern recognition signals¶
| When you see… | Think… |
|---|---|
| "merge overlapping" | Sort by start, extend the last end |
| "insert into sorted non-overlapping" | Three-phase linear scan |
| "min removals so none overlap", "max meetings attended", "min arrows" | Sort by end, greedy |
| "min rooms/platforms", "max concurrent" | Min-heap of ends, or sweep line events |
| "intersection of two lists of intervals" | Two pointers |
| "free time / gaps" | Merge, then look at gaps between merged ranges |
| "query: smallest interval containing point" | Offline: sort queries + heap |
| "calendar booking with dynamic inserts" | Sorted list + bisect, or a balanced tree |
Overlap rules (always clarify!)¶
For closed intervals [a, b] and [c, d]: they overlap iff a <= d and c <= b. For half-open [a, b): iff a < d and c < b. Touching endpoints such as [1,2] and [2,3] merge in Merge Intervals (closed) but do not conflict in Meeting Rooms (half-open).
Template 1: merge intervals¶
def merge(intervals):
intervals.sort(key=lambda x: x[0])
out = []
for s, e in intervals:
if out and s <= out[-1][1]:
out[-1][1] = max(out[-1][1], e)
else:
out.append([s, e])
return out
Template 2: insert interval (no sort needed, O(n))¶
def insert(intervals, new):
res, i, n = [], 0, len(intervals)
while i < n and intervals[i][1] < new[0]: # entirely before
res.append(intervals[i]); i += 1
while i < n and intervals[i][0] <= new[1]: # overlapping: absorb
new = [min(new[0], intervals[i][0]), max(new[1], intervals[i][1])]
i += 1
res.append(new)
res.extend(intervals[i:]) # entirely after
return res
Template 3: minimum rooms (heap) and sweep line¶
import heapq
def min_meeting_rooms(intervals):
intervals.sort()
ends = []
for s, e in intervals:
if ends and ends[0] <= s:
heapq.heapreplace(ends, e) # reuse the room
else:
heapq.heappush(ends, e) # need a new room
return len(ends)
def max_overlap(intervals): # sweep line
events = []
for s, e in intervals:
events.append((s, 1)); events.append((e, -1)) # end before start on ties: (e,-1) sorts first
events.sort()
cur = best = 0
for _, d in events:
cur += d; best = max(best, cur)
return best
Template 4: activity selection (sort by end)¶
def min_arrows(points):
points.sort(key=lambda p: p[1])
arrows, end = 0, float('-inf')
for s, e in points:
if s > end: # not covered by the last arrow
arrows += 1; end = e
return arrows
Template 5: interval list intersections (two pointers)¶
def interval_intersection(A, B):
i = j = 0; res = []
while i < len(A) and j < len(B):
lo, hi = max(A[i][0], B[j][0]), min(A[i][1], B[j][1])
if lo <= hi:
res.append([lo, hi])
if A[i][1] < B[j][1]: i += 1
else: j += 1 # advance the one that ends first
return res
Template 6: offline queries (Minimum Interval to Include Each Query)¶
def min_interval(intervals, queries):
intervals.sort()
h, res, i = [], {}, 0
for q in sorted(queries):
while i < len(intervals) and intervals[i][0] <= q:
l, r = intervals[i]
heapq.heappush(h, (r - l + 1, r)); i += 1
while h and h[0][1] < q: # expired: lazy deletion
heapq.heappop(h)
res[q] = h[0][0] if h else -1
return [res[q] for q in queries]
Technique table¶
| Problem | Sort key | Structure | Time |
|---|---|---|---|
| Merge intervals (56) | start | scan | O(n log n) |
| Insert interval (57) | none (sorted input) | scan | O(n) |
| Non-overlapping (435), arrows (452) | end | greedy | O(n log n) |
| Meeting rooms II (253) | start | min-heap of ends | O(n log n) |
| Max concurrent | events | sweep | O(n log n) |
| Intersections (986) | none | two pointers | O(n + m) |
| Min interval per query (1851) | start (+ queries) | heap + lazy delete | O((n+q) log n) |
| Employee free time (759) | start | merge, gaps | O(n log n) |
flowchart TD
A[Interval problem] --> B{Merge / union coverage?}
B -- yes --> C[Sort by start + extend]
B -- no --> D{Choose max subset without overlap / cover with fewest points?}
D -- yes --> E[Sort by END, greedy]
D -- no --> F{Count concurrent / rooms?}
F -- yes --> G[Heap of ends or sweep line]
F -- no --> H{Two sorted lists?}
H -- yes --> I[Two pointers]
H -- no --> J{Point queries?}
J -- yes --> K[Offline sort + heap, or bisect on merged]
Common bugs¶
- Sorting by the wrong key (start vs end).
- Closed vs half-open endpoint confusion. Sort event ties so ends come before starts when touching intervals don't conflict.
- Merging without
max()on the end (the case where one interval contains another). - Mutating input intervals unexpectedly, and aliasing when you append the input list itself.
- Heap tie comparisons on lists.
Resources¶
| Resource | Type | Why this one | Level | Cost |
|---|---|---|---|---|
| NeetCode roadmap: Intervals | video | Short, clear diagrams of each overlap case | intermediate | freemium |
| Tech Interview Handbook: Interval | article | Corner cases and problem list | intermediate | free |
| Jeff Erickson, ch. 4: Greedy (scheduling) |
book | Proof of interval scheduling and colouring | advanced | free |
| Python bisect docs | docs | Booking/calendar problems | intermediate | free |
| sortedcontainers docs |
docs | SortedList for dynamic interval sets | advanced | free |
| Hello Interview: coding patterns | interactive | Interval pattern walkthroughs | intermediate | freemium |
Hands-on lab¶
| # | LC | Problem | Difficulty | Set | Key insight |
|---|---|---|---|---|---|
| 1 | 252 | Meeting Rooms | Easy | NC150 | Sort by start; any overlap -> false. (Premium) |
| 2 | 56 | Merge Intervals | Medium | NC150 | Sort by start; extend last end with max. |
| 3 | 57 | Insert Interval | Medium | NC150 | Three phases: before, overlapping (merge), after. |
| 4 | 435 | Non-overlapping Intervals | Medium | NC150 | Sort by end; keep earliest-finishing (activity selection). |
| 5 | 452 | Minimum Number of Arrows to Burst Balloons | Medium | NC250+ | Sort by end; new arrow when start > last end. |
| 6 | 253 | Meeting Rooms II | Medium | NC150 | Min-heap of end times, or sweep +1/-1 events. (Premium) |
| 7 | 986 | Interval List Intersections | Medium | NC250+ | Two pointers; advance the one that ends first. |
| 8 | 1288 | Remove Covered Intervals | Medium | NC250+ | Sort (start asc, end desc); track max end. |
| 9 | 729 | My Calendar I | Medium | NC250+ | Sorted list + bisect for neighbour overlap check. |
| 10 | 1851 | Minimum Interval to Include Each Query | Hard | NC150 | Offline: sort queries, heap of (size, end) with lazy pops. |
| 11 | 759 | Employee Free Time | Hard | NC250+ | Merge all intervals; gaps are the answer. (Premium) |
Stretch exercise: implement MyCalendarThree (k-booking) with a sorted map of boundary deltas, then with a segment tree. Compare complexity as bookings grow to 10^5.
Questions¶
L1 — Recall¶
Q1. Why sort by end time for 'maximum non-overlapping intervals' but by start for merging?
Answer
Merging needs to see intervals in order of where they begin, so the current merged range can only be extended by intervals starting within it. Selection maximises the number kept, and picking the earliest finish leaves the most remaining room (exchange argument).
Q2. Two intervals overlap when…?
Answer
Closed: a <= d and c <= b. Half-open: a < d and c < b. Equivalent: not (one ends before the other starts).
Q3. Complexity of Insert Interval when the input is already sorted?
Answer
O(n): a single pass with three phases, no sorting.
L2 — Apply¶
Q4. Implement Non-overlapping Intervals.
Answer
See erase_overlap in the greedy page: sort by end, keep an interval when start >= last_end, and the answer is n - kept. O(n log n).
Q5. Trace Merge Intervals on [[1,3],[2,6],[8,10],[15,18]].
Answer
Sorted already. [1,3] starts the output. [2,6]: 2 ≤ 3 so end becomes 6. [8,10]: 8 > 6, so append. [15,18]: append. Result [[1,6],[8,10],[15,18]].
Q6. Implement My Calendar I.
Answer
from bisect import bisect_left
class MyCalendar:
def __init__(self): self.starts, self.ends = [], []
def book(self, s, e):
i = bisect_left(self.starts, s)
if i > 0 and self.ends[i - 1] > s: return False # previous overlaps
if i < len(self.starts) and self.starts[i] < e: return False
self.starts.insert(i, s); self.ends.insert(i, e)
return True
SortedList or a tree for O(log n) inserts.
L3 — Design & trade-offs¶
Q7. Meeting Rooms II: heap vs sweep vs sorted starts/ends two-pointer. Choose.
Answer
Heap of end times: intuitive, and it can also assign rooms. Sweep: minimal code, counts max concurrency only. Two sorted arrays (starts and ends) with two pointers: O(n log n) for the sorts, O(1) extra space beyond them. If asked to return the room assignment, use the heap. If the question is capacity only, use the sweep.
Q8. How do you handle intervals with millions of updates per second (a booking system)?
Answer
In-memory sorted structures don't scale across nodes. Partition by resource (a room, a vessel berth), because conflicts are per-resource. Within a partition use an interval tree or a sorted set. Correctness across concurrent requests needs serialisable per-resource checks: optimistic concurrency with a version, or a DB exclusion constraint (Postgres EXCLUDE USING gist with tstzrange), which enforces no-overlap atomically. State the trade-off: DB constraints are simple and correct but bound write throughput per resource.
Q9. When is a segment tree or interval tree needed instead of sorting?
Answer
When intervals are inserted/deleted dynamically and you need repeated stabbing queries ("which intervals contain t?") or range aggregates (k-booking max overlap). Sorting works for static or offline cases. An interval tree gives O(log n + k) stabbing queries. A segment tree with lazy propagation gives range add and max in O(log n).
L4 — Staff-level ambiguity¶
Q10. Design a meeting-room booking service for a 50,000-person company across 200 buildings, with recurring meetings and time zones.
Answer
Data model: rooms are the partition key, and events are stored with UTC ranges plus an RRULE for recurrence (expand lazily within a horizon window, store exceptions). Conflict check: the DB exclusion constraint over expanded occurrences in a materialised window (say 12 months), with a background job extending the window. Search ("find a free room for these attendees in this range"): per-building free/busy indexes, and merge attendees' busy intervals (the merge algorithm) then intersect with room availability (two pointers). Time zones and DST: store the zone id with recurrences and compute occurrences in local time. Scale: read-heavy, so cache the free/busy per room per day with invalidation on write. Talk about idempotent booking requests and fairness under contention.
Q11. Given 10^9 IP-range allow/deny rules, how would you check an IP quickly?
Answer
Normalise the rules to merged, non-overlapping, sorted ranges (Merge Intervals with precedence rules), then binary search the range list, O(log n). For frequent updates, use a radix trie (CIDR) or an interval tree. Memory: 10^9 ranges × 8–16 bytes is 8–16 GB, so shard by the top octets or use a compressed radix trie, since CIDR aggregation collapses adjacent ranges. In production, add a small hot-set cache. State the update model (batch rebuild plus atomic swap) and how to test rule precedence.
Real-world use cases¶
- Calendars and booking: meeting rooms, berth and yard slot booking, driver shifts.
- Time-series and billing: merging validity periods (tariffs with effective-from and effective-to dates), overlapping-price detection.
- Networking/security: IP allow-list range merging.
- Observability: merging alert-active windows to compute downtime.
Pitfalls & anti-patterns¶
- Not clarifying inclusive vs exclusive endpoints.
- Sorting by start when the greedy needs end.
- O(n²) pairwise overlap checks on large inputs.
- Ignoring time zones and DST in real-world recurrence.
Checklist¶
- I can write merge, insert, min-rooms and activity selection from memory
- I always state the endpoint convention before coding
- I solved 1851 with the offline heap approach
- I answered all L3 questions out loud in < 3 min each