Intervals: Ranges and Overlaps
- Read an interval as [start, end] and test whether two of them overlap
- See why sorting by start time is what makes every interval problem cheap
- Merge, insert, intersect, and count concurrent intervals in one pass
Interval Basics
Strip the wording off any interval problem and the same question is hiding underneath: do these two ranges touch? An interval is a pair [start, end], a meeting from 9 to 11, a process alive from time 3 to time 7. Learn to answer the overlap question cheaply and everything else in this section is a variation on that one skill.
Overlap conditions (assume start <= end). [1, 5] and [3, 7] overlap because B starts before A ends. [1, 5] and [6, 7] don't, because B starts after A ends. In full generality two intervals overlap when A.start <= B.end AND B.start <= A.end. Once you've sorted by start you already know A came first, so that whole condition collapses to a single check: curr.start <= prev.end.
- O(n log n) Sort. Always sort first.
- O(n) Merge. Single pass after sort.
- O(n) Insert. Three-phase scan.
- O(n log n) Min Rooms. Sort + heap.
Why Sorting Is Everything
Sorting is the first move, every single time. Leave the intervals unsorted and any one of them can overlap any other, which means comparing every pair at O(n²). Sort by start time and the overlapping ones land next to each other, so a single left-to-right pass catches all of them in O(n).
Why does this hold? If intervals are sorted by start time and interval A comes before interval B, then A.start <= B.start. The only question left is whether A's range extends far enough to reach B. If A.end >= B.start, they overlap. If not, nothing after B can overlap with A either, because everything after B starts even later. Once you find a gap, you never look backwards. That one fact drops Merge Intervals from O(n²) brute force to O(n log n).
The Merge Process
Merge Intervals puts sorting to work. Once the list is sorted, an overlap is always the next interval in line.
Recognizing Interval Problems
Merge overlapping intervals
Sort + extend
Sort by start, merge adjacent overlaps
Insert an interval
Three phases
Before, merge overlaps, after
Find intersections
Two pointers
Advance the pointer that ends first
Can attend all meetings?
Sort + conflict check
Any overlap = conflict
Minimum rooms needed
Heap or sweep line
Track concurrent meetings
The Two Core Patterns
Merge & Insert
Combine overlapping intervals
Sort by start time, then sweep left to right. If current overlaps with previous, extend. Otherwise, start a new interval.
Scheduling
Handle conflicts and resources
Use a heap to track end times of ongoing events, or use sweep line to count events at each moment.
Merge Intervals
Given a list of intervals, merge all overlapping intervals. Sort first, then sweep left to right comparing each interval with the last merged one:
def merge(intervals): if not intervals: return [] intervals.sort(key=lambda x: x[0]) # Sort by start time merged = [intervals[0]] # Start with first interval for current in intervals[1:]: last = merged[-1] if current[0] <= last[1]: # Overlapping — extend last[1] = max(last[1], current[1]) else: # Gap — start new interval merged.append(current) return mergedOne comparison decides it. If the current interval starts before or at the point where the previous one ends, they overlap, and you extend by taking the larger end.
Visual: Input: [---] [------] [----] [----] 1 3 2 6 8 10 15 18 Merged: [----------] [----] [----] 1 6 8 10 15 18Insert Interval
Insert a new interval into a sorted list of non-overlapping intervals, merging if necessary. Process the intervals in three phases: before, overlap, and after.
def insert(intervals, newInterval): result = [] i, n = 0, len(intervals) # Phase 1: add intervals that end before newInterval starts while i < n and intervals[i][1] < newInterval[0]: result.append(intervals[i]) # No overlap possible i += 1Phase 2 merges all overlapping intervals into the new one by expanding its boundaries:
# Phase 2: merge all overlapping intervals while i < n and intervals[i][0] <= newInterval[1]: newInterval[0] = min(newInterval[0], intervals[i][0]) # Expand left newInterval[1] = max(newInterval[1], intervals[i][1]) # Expand right i += 1 result.append(newInterval) # Add the merged intervalPhase 3 just appends everything that comes after:
# Phase 3: add remaining intervals while i < n: result.append(intervals[i]) # These start after newInterval ends i += 1 return resultThe three phases stay separate because the intervals arrive sorted. Each phase owns one non-overlapping region.
Interval Intersection
Find the intersection of two sorted interval lists by walking both with two pointers. For each pair you land on, the intersection is [max(starts), min(ends)].
def interval_intersection(firstList, secondList): result = [] i = j = 0 while i < len(firstList) and j < len(secondList): a_start, a_end = firstList[i] b_start, b_end = secondList[j] start = max(a_start, b_start) # Intersection starts at later start end = min(a_end, b_end) # Intersection ends at earlier endIf the intersection is valid, add it. Then advance whichever pointer ends first, since that interval can't intersect with anything else.
if start <= end: # Valid intersection exists result.append([start, end]) if a_end < b_end: # Advance the one that ends first i += 1 else: j += 1 return resultMeeting Rooms
Can a person attend all meetings? Sort by start time and check if any meeting starts before the previous one ends:
def can_attend_meetings(intervals): intervals.sort(key=lambda x: x[0]) # Sort by start time for i in range(1, len(intervals)): if intervals[i][0] < intervals[i-1][1]: # Starts before prev ends return False # Conflict — can't attend both return True # No overlaps foundAny overlap means a conflict. After sorting, you only need to compare consecutive intervals. Non-adjacent ones can't overlap if adjacent ones don't.
Meeting Rooms II (Min Rooms)
What's the minimum number of meeting rooms needed? Use a min-heap to track when rooms free up. Sort by start time, then process each meeting:
import heapqdef min_meeting_rooms(intervals): if not intervals: return 0 intervals.sort(key=lambda x: x[0]) # Sort by start time rooms = [] # Min-heap of end times heapq.heappush(rooms, intervals[0][1]) # First meeting gets a roomFor each subsequent meeting, check if the earliest-ending room is free. If so, reuse it; otherwise, allocate a new room:
for i in range(1, len(intervals)): if intervals[i][0] >= rooms[0]: # Earliest room is free heapq.heappop(rooms) # Reuse that room heapq.heappush(rooms, intervals[i][1]) # Assign room return len(rooms) # Rooms still in use = answerThe heap size at any point equals the number of rooms in use. The final heap size is the maximum concurrent meetings.
What This Section Covers
Non-overlapping, remove covered, partition
Sort First, Compare Neighbors
Every interval problem opens on the same line: sort by start time so the overlapping intervals sit next to each other. After that, a single left-to-right pass does the real work, whether you're merging, inserting, or checking for a conflict.
The operations are all small once the sort is in place. Merge extends the previous end to the max of both. Insert splits the timeline into three regions: everything before, the overlap you fold in, and everything after. Intersection walks two pointers and advances whichever interval ends first. Minimum rooms keeps a min-heap of end times, and the heap size is your answer.
Key Takeaways
- Always sort by start time first
- Overlap check: current.start <= previous.end
- Merge: extend previous.end = max(ends)
- Insert: three phases (before, merge, after)
- Min rooms: heap of end times or sweep line