Sort the intervals by start value first — that single step guarantees any overlap only happens with the immediately preceding merged interval, enabling a clean O(n) sweep instead of an O(n²) nested comparison.
Given an array of intervals [[start, end], …], merge all overlapping intervals and return the result. Two intervals overlap when one starts at or before the other ends.
Example: [[1,3],[2,6],[8,10],[15,18]] → [[1,6],[8,10],[15,18]] because [1,3] and [2,6]overlap (2 ≤ 3), so they fuse into [1,6]. The remaining two pairs have gaps, so they stay separate.
current.start against last.end. One forward pass, no backtracking.intervals.sort((a, b) => a[0] - b[0]). This is the enabling step — overlaps become local (adjacent).[start, end]:start <= last.end — overlap: update last.end = max(last.end, end). Use maxbecause a fully-contained interval wouldn't extend the end.[start, end] as a new entry.Math.max when extending. If the new interval is fully contained within the last merged one (e.g. [1,10] followed by [2,5]), a naive last.end = end would incorrectly shrink the result to [1,5].The sweep itself is O(n) — each interval is pushed or merged exactly once. Total is O(n log n) for the sort. In-place mutation of the input is possible but messy; the idiomatic approach uses a separate output array.
[1,3], [2,6], [8,10], [15,18]. First we sort by start value — that is the key step that makes a single sweep possible.function merge(intervals: number[][]): number[][] {
// Sort by start so any overlap must be with the immediately previous merged interval
intervals.sort((a, b) => a[0] - b[0]);
const merged: number[][] = [];
for (const [start, end] of intervals) {
if (merged.length === 0) {
merged.push([start, end]);
continue;
}
const last = merged[merged.length - 1];
if (start <= last[1]) {
// Overlapping: extend the end if needed
last[1] = Math.max(last[1], end);
} else {
// Gap: start a fresh interval
merged.push([start, end]);
}
}
return merged;
}[start, end]must be the one immediately before it in the sorted list. A later interval can't start earlier, so it can't retroactively extend an already-closed gap.[start, end] unconditionally and continue to the next.start <= last[1] means the current interval begins inside or at the boundary of the last merged one. Extend the end with Math.max(last[1], end) — the max handles full containment (e.g. [1,10] swallowing [2,5]).start > last[1]means there's a real gap. The previous merged interval is now closed forever; push a fresh one.| "merge overlapping intervals" | sort by start → sweep last.end |
| start ≤ last end | overlap: extend last[1] = max(last[1], end) |
| start > last end | gap: push new interval |
| "insert/remove/count intervals" | same sort-by-start skeleton |
function merge(intervals: number[][]): number[][] {
intervals.sort((a, b) => a[0] - b[0]); // sort by start
const merged: number[][] = [];
for (const [start, end] of intervals) {
const last = merged[merged.length - 1];
if (!last || start > last[1]) {
merged.push([start, end]); // no overlap: new interval
} else {
last[1] = Math.max(last[1], end); // overlap: extend end
}
}
return merged;
}current.start against last.end. What does current.start <= last.end mean?[1,10] and the next sorted interval is [3,5]. What should the merged list contain after processing [3,5]?[[1,4],[4,5]]. What is the output?