Problem Statement

Merge Intervals

You get a list of intervals. An interval is just a start and an end, like [1,3], which you can think of as a time block that starts at 1 and ends at 3. Some of these blocks overlap, meaning they share part of the same range. Your job is to combine every group of overlapping blocks into one bigger block, and return the cleaned-up list. Two blocks overlap when one starts before the other one ends, so [1,3] and [2,6] overlap and become [1,6]. The trick is to sort the blocks by their start time first, then walk through them in order and glue overlapping ones together by stretching the end.

mediumGreedySortingGreedyTime: O(n log n) · Space: O(n)

Signals to notice

merge overlapping intervalssort by startextend or start new

Brute force first

Compare every pair for overlap. Doesn't exploit sorted structure. That instinct is useful because it follows the prompt literally, but it usually keeps revisiting work the problem is begging you to organize.

The key insight

Sort by start time. Scan left to right: if the current interval overlaps with the last merged one, extend it. Otherwise, start a new merged interval. Instead of recomputing the world every time, you preserve just enough context to let the next decision become obvious.

Trace it on intervals=[[1,3],[2,6],[8,10],[15,18]]

sort by start -> [[1,3],[2,6],[8,10],[15,18]] (already sorted); result=[[1,3]]
i=1 [2,6]: 2<=3 overlap -> end=max(3,6)=6; result=[[1,6]]
i=2 [8,10]: 8<=6? no -> append; result=[[1,6],[8,10]]
i=3 [15,18]: 15<=10? no -> append; result=[[1,6],[8,10],[15,18]]
loop ends -> return [[1,6],[8,10],[15,18]]

What must stay true

After sorting by start, overlapping intervals are adjacent. If the current start ≤ the previous end, they overlap — merge by extending the end to max(prevEnd, currEnd). As long as that statement keeps holding, you can trust the steps built on top of it.

Shape of the loop

sort intervals by start
result = [intervals[0]]
for each interval after the first:
    if interval.start <= result.last.end:   # overlap
        result.last.end = max(result.last.end, interval.end)
    else:
        result.append(interval)
return result

Pseudocode only — the full worked solution lives in the Solution tab.

Easy way to go wrong

Not taking the max of ends when merging — if interval B is entirely contained within interval A, the merged end should be A's end, not B's. Most mistakes here are not about syntax; they come from losing track of what your state, pointer, or structure is supposed to mean.

Greedy Pattern