Merge an array of intervals so there are no overlapping intervals, and return the resultant merged intervals.
Input: intervals = [[3, 4], [7, 8], [2, 5], [6, 7], [1, 4]]
Output: [[1, 5], [6, 8]]
The input contains at least one interval.
For every index i in the array, intervals[i].start ≤ intervals[i].end.
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Imagine your calendar has a bunch of overlapping busy blocks — a call from 1–4, another from 2–5, a quick sync 3–4. To a colleague looking for a free slot, that entire mess is just “busy 1–5.” Merging intervals is exactly this clean-up: fuse everything that touches or overlaps into one solid busy block, so the schedule shows the fewest, widest blocks possible.
There are two main challenges to this problem:
Let's start by tackling the first challenge. Consider two intervals, A and B, where interval A starts before B. Below, we visualize the case when these two intervals don't overlap:
The dashed line above shows that interval A ends before interval B starts, which eliminates the possibility of B overlapping A. This indicates that intervals A and B will never overlap when A.end < B.start.
Now, consider a couple of cases where these two intervals do overlap:
In these cases, we see that B starts before (or when) A ends (A.end ≥ B.start). In other words, some portion of B overlaps A since interval A hasn't ended before interval B starts. Therefore, intervals A and B overlap when A.end ≥ B.start.
We have now established the two cases that cover all overlapping and non-overlapping scenarios for two intervals, given interval A starts before interval B:
A.end < B.start, the intervals don't overlap.A.end ≥ B.start, the intervals overlap.To apply these conditions to any two intervals in the input, it's useful to have a way to identify which interval starts first. One idea is to sort the intervals by their start value, which will make it clear which one of each two adjacent intervals starts first.
Merging intervals
With the above logic in mind, let's tackle an example. Consider the following intervals:
The first step is to sort these intervals by start value:
To aid the explanation, let's represent the intervals visually:
Let's add/merge each interval into a new array called merged, starting with the first one, which we can add to the merged array straight away as it's the first interval:
After the first interval is added, we start the process of merging. Let's define A as the last interval in the merged array and B as the current interval in the input. This makes sense since the interval in the merged array (A) starts before or at the same time as B.
We notice B starts before A ends, indicating an overlap. So, let's merge them:
When merging A and B, we use the leftmost start value and the rightmost end value between them. Since A will always start before or at the same time as B, we always use A.start as the start point. This means we just need to identify the end point, which is the largest value between the end points of A and B:
We can apply the same logic to the next interval:
When we reach the fourth interval, we notice B starts after A ends, indicating there is no overlap.
So, we just add it as a new interval to the merged array:
The next interval, B, overlaps the last interval in the merged array, A, since B starts when A ends (A.end == B.start).
So, let's merge A with B:
After processing the last interval, we've successfully merged all intervals.
from typing import List
from ds import Interval
def merge_overlapping_intervals(intervals: List[Interval]) -> List[Interval]:
intervals.sort(key=lambda x: x.start)
merged = [intervals[0]]
for B in intervals[1:]:
A = merged[-1]
# If A and B don't overlap, add B to the merged list.
if A.end < B.start:
merged.append(B)
# If they do overlap, merge A with B.
else:
merged[-1] = Interval(A.start, max(A.end, B.end))
return mergedvector<Interval> mergeOverlappingIntervals(vector<Interval> intervals) {
if (intervals.empty()) {
return {};
}
sort(intervals.begin(), intervals.end(),
[](const Interval& a, const Interval& b){ return a.start < b.start; });
vector<Interval> merged;
merged.push_back(intervals[0]);
for (int i = 1; i < (int)intervals.size(); i++) {
Interval B = intervals[i];
if (merged.back().end < B.start) {
merged.push_back(B); // gap: B starts a new run
} else {
merged.back().end = max(merged.back().end, B.end); // overlap: extend the run
}
}
return merged;
}List<Interval> mergeOverlappingIntervals(List<Interval> intervals) {
if (intervals.isEmpty()) return new ArrayList<>();
intervals.sort((a, b) -> a.start - b.start);
List<Interval> merged = new ArrayList<>();
merged.add(intervals.get(0));
for (int i = 1; i < intervals.size(); i++) {
Interval B = intervals.get(i);
Interval A = merged.get(merged.size() - 1);
if (A.end < B.start) merged.add(B);
else merged.set(merged.size() - 1, new Interval(A.start, Math.max(A.end, B.end)));
}
return merged;
}Time complexity: The time complexity of merge_overlapping_intervals is , where denotes the number of intervals. This is due to the sorting algorithm. The process of merging overlapping intervals itself takes time because we iterate over every interval.
Space complexity: The space complexity depends on the space used by the sorting algorithm. In Python, the built-in sorting algorithm, Tim sort, uses space. Note that the merged array is not considered in the space complexity calculation because we're only concerned with extra space used, not space taken up by the output.
Tip: Visualize intervals to uncover logic and edge cases.
Managing intervals and handling edge cases is much easier when visualizing example inputs. Drawing examples also helps your interviewer follow along with your reasoning and understand your thought process.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
[[3,4],[7,8],[2,5],[6,7],[1,4]]Step 0 — sort by start → [[1,4],[2,5],[3,4],[6,7],[7,8]]. Here is the sorted picture on a number line (result at the bottom):
Overlapping or touching intervals collapse into one run — the five inputs merge into [1,5] and [6,8] (green).
finalized run current run (tail A) just extended
[[1,5], [6,8]] ✓
| B (incoming) | A (tail) | A.end ≥ B.start? | action | answer after |
|---|---|---|---|---|
| — (seed) | — | — | start run [1,4] | [1,4] |
| [2,5] | [1,4] | 4≥2 yes | extend → max(4,5)=5 | [1,5] |
| [3,4] | [1,5] | 5≥3 yes | extend → max(5,4)=5 | [1,5] |
| [6,7] | [1,5] | 5≥6 no | gap → new run | [1,5],[6,7] |
| [7,8] | [6,7] | 7≥7 yes | extend → max(7,8)=8 | [1,5],[6,8] |
[[8,9],[1,3],[4,6]]Step 0 — sort by start → [[1,3],[4,6],[8,9]]. Every interval sits in its own lane; watch the near-miss between 3 and 4.
[1,3] ends at 3 and [4,6] starts at 4 — a gap of 1, so they do not merge. The result is unchanged: [1,3], [4,6], [8,9].
finalized run current run (tail A) gap — no merge
[[1,3], [4,6], [8,9]] — identical to the input ✓
| B (incoming) | A (tail) | A.end ≥ B.start? | action | answer after |
|---|---|---|---|---|
| — (seed) | — | — | start run [1,3] | [1,3] |
| [4,6] | [1,3] | 3≥4 no | gap → new run | [1,3],[4,6] |
| [8,9] | [4,6] | 6≥8 no | gap → new run | [1,3],[4,6],[8,9] |
Take-away: the merge decision hinges on a single comparison. A gap of even 1 (3 then 4, on closed integer intervals) is still a gap. Get the < vs ≤ boundary right and this problem is solved.
intervals.sort(key=lambda x: x.start) sorts in place. The answer lives in merged; to extend the run we replace its last element with merged[-1] = Interval(A.start, max(A.end, B.end)).
A comparator lambda a.start < b.start drives sort. Because merged stores values, we extend by writing straight into merged.back().end.
intervals.sort((a,b) -> a.start - b.start) orders by start. To extend we build a fresh Interval and merged.set(last, new Interval(...)). The logic is line-for-line the same as Python and C++.