Intervals & Line Sweep
Turn an unordered pile of ranges into one ordered left-to-right scan so that merging, scheduling and "how many at once" questions collapse into a linear pass after sorting.
Instinct See [start, end] pairs, meetings, bookings or the word "overlap"? Sort by an endpoint, then sweep left to right carrying one running fact.
Memory hack Sort, then sweep - start time to merge, end time to choose, +1/-1 events to count the crowd.
At a glance
When to use it
- The input is a list of [start, end] ranges such as meetings, bookings or events
- You must merge, insert or union overlapping ranges
- You must keep the most ranges or remove the fewest so none overlap
- You need the peak number of ranges active at the same moment
Key idea
Sort once by the endpoint the question cares about (start to merge, end to pick greedily, or split into +1/-1 events) so each range only meets the ones still open; a single sweep then replaces checking every pair in O(n^2), giving O(n log n) overall.
Typical complexity
O(n log n) time, O(n) space
| # | Common problems | Level | Our solution | Practice |
|---|---|---|---|---|
| 1 | Meeting Rooms | Easy | Read solution | LeetCode ↗ |
| 2 | Merge Intervals | Medium | Read solution | LeetCode ↗ |
| 3 | Insert Interval | Medium | Read solution | LeetCode ↗ |
| 4 | Non-overlapping Intervals | Medium | Read solution | LeetCode ↗ |
| 5 | Meeting Rooms II | Medium | Read solution | LeetCode ↗ |
| 6 | Minimum Interval to Include Each Query | Hard | Read solution | LeetCode ↗ |
The instinct
Ranges on a number line are only hard while they are shuffled. The moment you sort them, every interval can only interact with the ones that are "still open" when it arrives, so you can walk left to right and keep a tiny summary: the last merged interval, the earliest finishing time, or a counter of how many ranges are active right now.
Almost every interval problem is one of three moves: merge (sort by start), pick the most (sort by end, greedy), or count concurrency (split into +1/-1 events and sweep).
Recognise it
| Signal in the problem | Why it points here |
|---|---|
Input is int[][] of [start, end], or meetings, bookings, events, tasks with times |
Ranges on a line - the defining shape of the pattern |
| "Merge all overlapping", "insert into sorted non-overlapping list" | Sort by start; each new range either extends the last one or starts a new block |
| "Minimum number to remove so the rest don't overlap", "minimum arrows" | Classic activity selection: greedily keep the range that ends earliest |
| "Minimum rooms / platforms / servers", "maximum concurrent" | Count overlaps at the busiest moment: sweep events or a min-heap of end times |
| "Can a person attend all meetings?" | Sort and check each neighbour pair for overlap |
| "For each query point, which interval covers it / smallest covering interval" | Sort intervals and queries together, sweep, and keep candidates in a heap |
| Skyline, union length, painted segments | Events at critical x-coordinates plus a structure holding the active heights |
Not this pattern if...
- The ranges are index windows over one array you build yourself (that is sliding window or prefix sums).
- Intervals arrive one at a time and must be queried online (that leans to an ordered set / TreeMap, e.g. My Calendar).
- You need range updates and range queries on values many times (segment tree / Fenwick territory).
Memory hack
"Start to merge, end to choose, events to count." Sorting by start lines intervals up so neighbours touch; sorting by end tells you who frees the line first; turning ranges into +1/-1 events lets you count the crowd at any moment.
Analogy - the hotel front desk. Guests (intervals) check in and check out. To know how many rooms you need, the clerk does not study each guest; she just walks through the day's timeline, adding one at every check-in and subtracting one at every check-out. The highest number on her tally is the answer. To fit the most guests in one room, she always gives the room to whoever leaves earliest.
Say it out loud in the interview:
- "These are ranges, so I will sort them - by start if I merge, by end if I greedily keep as many as possible."
- "Two ranges overlap when
a.start < b.end && b.start < a.end- let me confirm whether touching endpoints count." - "One pass, carrying only the last kept interval / an end-time heap / an active counter."
How it works
After sorting by start, an interval can overlap the current merged block only if its start is not past the block's end. If it overlaps, stretch the block's end; otherwise close the block and open a new one. Trace for Merge Intervals on [[1,3],[8,10],[2,6],[15,18],[9,12]]:
| Step | Sorted interval | Last block before | Overlap? | Output after |
|---|---|---|---|---|
| 1 | [1,3] | - | - | [1,3] |
| 2 | [2,6] | [1,3] | 2 <= 3 yes | [1,6] |
| 3 | [8,10] | [1,6] | 8 > 6 no | [1,6] [8,10] |
| 4 | [9,12] | [8,10] | 9 <= 10 yes | [1,6] [8,12] |
| 5 | [15,18] | [8,12] | 15 > 12 no | [1,6] [8,12] [15,18] |
For counting, convert each [s, e) into (s, +1) and (e, -1), sort by time, and break ties so that -1 comes first when touching endpoints do not overlap.
flowchart TD
A["Ranges given"] --> B{"What is asked?"}
B -->|"merge / insert / union"| C["Sort by start, extend last block"]
B -->|"keep max / remove min / arrows"| D["Sort by end, greedy keep earliest finisher"]
B -->|"rooms / max overlap"| E["+1 / -1 events or min-heap of ends"]
B -->|"queries or skyline"| F["Sort events + heap of active items"]
Java template
import java.util.*; class IntervalTemplates { // 1) MERGE: sort by start, extend or close the last block. static int[][] merge(int[][] intervals) { Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); List<int[]> out = new ArrayList<>(); for (int[] cur : intervals) { int[] last = out.isEmpty() ? null : out.get(out.size() - 1); if (last == null || cur[0] > last[1]) { // use >= if touching ends must stay apart out.add(new int[]{cur[0], cur[1]}); } else { last[1] = Math.max(last[1], cur[1]); // overlap: stretch the block } } return out.toArray(new int[0][]); } // 2) GREEDY SELECT: max non-overlapping set (remove = n - kept). static int maxNonOverlapping(int[][] intervals) { Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1])); int kept = 0; long lastEnd = Long.MIN_VALUE; for (int[] cur : intervals) { if (cur[0] >= lastEnd) { // use > if touching ends overlap kept++; lastEnd = cur[1]; } } return kept; } }
import java.util.*; class SweepTemplate { // 3) SWEEP: maximum number of simultaneously active half-open ranges [s, e). static int maxConcurrent(int[][] intervals) { int n = intervals.length; int[][] events = new int[2 * n][]; for (int i = 0; i < n; i++) { events[2 * i] = new int[]{intervals[i][0], +1}; events[2 * i + 1] = new int[]{intervals[i][1], -1}; } // same time: process -1 (leave) before +1 (arrive) Arrays.sort(events, (a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1])); int active = 0, best = 0; for (int[] ev : events) { active += ev[1]; best = Math.max(best, active); // record any per-moment fact here } return best; } // Same answer with a min-heap of end times (Meeting Rooms II style). static int minRooms(int[][] intervals) { Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); PriorityQueue<Integer> ends = new PriorityQueue<>(); for (int[] cur : intervals) { if (!ends.isEmpty() && ends.peek() <= cur[0]) ends.poll(); // reuse a freed room ends.offer(cur[1]); } return ends.size(); } }
Complexity
| Technique | Time | Space |
|---|---|---|
| Merge / insert (sorted input for insert) | O(n log n) sort + O(n) pass; insert alone is O(n) | O(n) output |
| Greedy by end | O(n log n) | O(1) extra (sort aside) |
| Event sweep | O(n log n) for 2n events | O(n) |
| Heap of end times | O(n log n) | O(n) |
| Sweep + heap with q queries | O((n + q) log(n + q)) | O(n + q) |
Variations
- Merge / union - sort by start, stretch the last block. Merge Intervals, union length of segments.
- Insert into sorted list - copy everything ending before the new one, absorb all overlapping ones, copy the rest; no sort needed. Insert Interval.
- Activity selection (greedy by end) - keep the earliest finisher. Non-overlapping Intervals, Minimum Number of Arrows to Burst Balloons (touching counts as overlap there).
- Concurrency counting - events or end-time heap. Meeting Rooms II, car pooling, max platforms.
- Sweep with a heap of candidates - sort by start, push into a heap while sweeping time or queries. Maximum Number of Events That Can Be Attended, Minimum Interval to Include Each Query.
- Skyline / geometric sweep - critical x-points plus a max-structure of active heights. The Skyline Problem.
- Difference array - when coordinates are small integers,
diff[s]++,diff[e]--, then prefix-sum.
Common mistakes
- Sorting by the wrong key: merging wants start, maximising kept intervals wants end. Sorting greedy problems by start fails on one long early interval.
- Not clarifying closed vs half-open ranges, so
[1,2]and[2,3]are merged (or counted) incorrectly. Ask, then pick<or<=deliberately. - Writing
last[1] = cur[1]instead ofMath.max(last[1], cur[1])- a fully contained interval would shrink the block. - Wrong tie-break in event sweeps: if ends and starts at the same time are not ordered correctly, the peak is over- or under-counted.
(a, b) -> a[0] - b[0] overflows when values span close to Integer.MIN_VALUE/MAX_VALUE (balloon coordinates are a famous trap). Always use Integer.compare.
- Mutating the input array or returning references to it when the caller expects fresh output - copy intervals as you add them.
All practice problems
9 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.