Intervals
Interval problems involve ranges [start, end] that may overlap, touch, or be disjoint. Master sort-and-merge, sweep line, and end-sort greedy techniques to efficiently merge, count, insert, and remove intervals.
Learn & Reference
Understanding the Pattern
Intervals Pattern
Interval problems deal with ranges [start, end] — scheduling meetings, merging time slots, or finding overlaps. Nearly every interval problem starts the same way: sort the intervals, then process them with a single scan.
Intervals: |----| |------| |--|
Sorted: |--| |----| |------| |--|
Merge: |--| |--------| |--|
The key insight: once intervals are sorted, overlapping ranges become adjacent in the array, making them easy to detect and process.
Overlap Detection — The Foundation
Two intervals [a, b] and [c, d] overlap when:
a < d AND c < b (strict — touching doesn't count)
a <= d AND c <= b (inclusive — touching counts as overlap)
Which version to use depends on the problem. Read carefully whether touching intervals should be merged or kept separate.
Five Core Techniques
1. Sort + Merge
Pattern: Sort by start time, then sweep left to right merging overlapping intervals.
Input: [1,3] [2,6] [8,10] [15,18]
Sort: [1,3] [2,6] [8,10] [15,18] (already sorted)
Process: [1,3] + [2,6] → overlap (2 ≤ 3) → merge to [1,6]
[1,6] + [8,10] → no overlap (8 > 6) → new group
[8,10] + [15,18] → no overlap → new group
Result: [1,6] [8,10] [15,18]
When to use: "merge all overlapping intervals", "combine ranges"
2. Insert + Merge
Pattern: Insert a new interval into a sorted list. Three phases:
- Add all intervals that end before the new one starts
- Merge all intervals that overlap with the new one
- Add all intervals that start after the new one ends
When to use: "insert interval into sorted list", "add a new range"
3. End-Sort Greedy
Pattern: Sort by end time (not start). Greedily select intervals that finish earliest to maximize non-overlapping selections, or equivalently minimize removals.
Sorted by end: [1,2] [1,3] [2,3] [3,4]
Select [1,2] → skip [1,3] (overlaps) → select [2,3] → select [3,4]
Kept: 3 intervals, removed: 1
When to use: "minimum intervals to remove", "maximum non-overlapping", "minimum arrows/shots"
4. Sweep Line
Pattern: Create events for starts (+1) and ends (-1). Sort events by time. Sweep through to find maximum concurrent count.
Meetings: [0,30] [5,10] [15,20]
Events: (0,+1) (5,+1) (10,-1) (15,+1) (20,-1) (30,-1)
Sweep: 0→1, 5→2, 10→1, 15→2, 20→1, 30→0
Max concurrent: 2 → need 2 rooms
Alternative (more efficient): sort starts and ends separately, use two pointers.
When to use: "minimum rooms/resources", "maximum overlapping at any point"
5. Overlap Detection
Pattern: Check if any two intervals overlap, or find all pairs that overlap. Foundation for all other techniques.
[a, b] overlaps [c, d] ⟺ a < d AND c < b
When to use: "do any meetings conflict?", "find overlapping ranges"
Common Mistakes
- Off-by-one with touching intervals —
[1,2]and[2,3]: some problems treat this as overlapping (merge), others as non-overlapping (keep separate). Read the problem statement carefully. - Not sorting before processing — Almost every interval problem requires sorting first. Forgetting this leads to wrong answers on unsorted input.
- Sorting by the wrong key — Sort by start for merge problems, sort by end for greedy selection problems. Using the wrong key breaks the invariant.
- Integer overflow in comparators — In Java,
a[0] - b[0]overflows for extreme values. Always useInteger.compare(a[0], b[0]). - Mutating input vs building new array — Some problems expect a new result array; others can modify in-place. Be clear about what you're returning.
- Confusing "can touch" with "can overlap" —
start <= end(touching counts) vsstart < end(strict overlap). One character difference, completely different results.
Template
1# --- Sort + Merge Template ---2def solve(intervals):3intervals.sort()4merged = [intervals[0]]5for start, end in intervals[1:]:6if start <= merged[-1][1]: # Overlapping7merged[-1][1] = max(merged[-1][1], end)8else:9merged.append([start, end])10return merged1112# --- Sweep Line Template ---13def max_concurrent(intervals):14starts = sorted(s for s, e in intervals)15ends = sorted(e for s, e in intervals)16rooms = 017max_rooms = 018s = e = 019while s < len(intervals):20if starts[s] < ends[e]:21rooms += 122max_rooms = max(max_rooms, rooms)23s += 124else:25rooms -= 126e += 127return max_rooms
Syntax Reference
1# --- Sorting intervals ---2intervals.sort() # Sort by start (default tuple sort)3intervals.sort(key=lambda x: x[1]) # Sort by end45# --- Unpacking ---6for start, end in intervals: # Destructure each interval78# --- Building result ---9merged = [intervals[0]]10merged.append([start, end])11merged[-1][1] = max(merged[-1][1], end) # Extend last interval1213# --- Overlap check ---14# Inclusive: start <= prev_end15# Strict: start < prev_end
Common Recipes
1# Recipe 1: Merge Overlapping Intervals2def merge(intervals):3intervals.sort()4merged = [intervals[0]]5for start, end in intervals[1:]:6if start <= merged[-1][1]:7merged[-1][1] = max(merged[-1][1], end)8else:9merged.append([start, end])10return merged1112# Recipe 2: Sweep Line — Max Concurrent (Meeting Rooms II)13def min_meeting_rooms(intervals):14starts = sorted(s for s, e in intervals)15ends = sorted(e for s, e in intervals)16rooms = 017max_rooms = 018s = e = 019while s < len(intervals):20if starts[s] < ends[e]:21rooms += 122max_rooms = max(max_rooms, rooms)23s += 124else:25rooms -= 126e += 127return max_rooms2829# Recipe 3: End-Sort Greedy — Min Arrows / Max Non-overlapping30def find_min_arrow_shots(points):31points.sort(key=lambda x: x[1])32arrows = 133arrow_pos = points[0][1]34for start, end in points[1:]:35if start > arrow_pos:36arrows += 137arrow_pos = end38return arrows
Complexity
| Technique | Time | Space | Example |
|---|---|---|---|
| Sort + Merge | O(n log n) | O(n) | Merge Intervals |
| Insert + Merge | O(n) | O(n) | Insert Interval |
| End-Sort Greedy | O(n log n) | O(1) | Non-overlapping Intervals |
| Sweep Line | O(n log n) | O(n) | Meeting Rooms II |
| End-Sort + Count | O(n log n) | O(1) | Burst Balloons |
Watch Out
- ✗Off-by-one with touching intervals — `[1,2]` and `[2,3]`: some problems treat this as overlapping (merge), others as non-overlapping (keep separate). Read the problem statement carefully.
- ✗Not sorting before processing — Almost every interval problem requires sorting first. Forgetting this leads to wrong answers on unsorted input.
- ✗Sorting by the wrong key — Sort by start for merge problems, sort by end for greedy selection problems. Using the wrong key breaks the invariant.
- ✗Integer overflow in comparators — In Java, `a[0] - b[0]` overflows for extreme values. Always use `Integer.compare(a[0], b[0])`.
- ✗Mutating input vs building new array — Some problems expect a new result array; others can modify in-place. Be clear about what you're returning.
- ✗Confusing "can touch" with "can overlap" — `start <= end` (touching counts) vs `start < end` (strict overlap). One character difference, completely different results.