Intervals
Sort by start to merge and count overlaps; sort by end to choose. One sorted pass replaces checking every pair.
When it applies
Use the interval pattern whenever the input is a list of ranges: bookings, meetings, trading sessions, time windows, segments on a line. The pairwise check, does this one overlap that one, is O(n²); sorting first makes every overlap appear next to the range it overlaps, and one pass handles them.
Which sort depends on the question. Sort by start to merge ranges or measure what they cover: a range that starts after the current block ends opens a new block. Sort by end to choose the most non-overlapping ranges: the one that ends earliest leaves the most room. Sort starts and ends separately to count how many are open at once, as a sweep line over +1 and −1 events.
Signals: "intervals", "bookings", "meetings", "overlap", "merge", "rooms", "schedule", "covered". When the ranges carry values and you must choose the best set, the order still comes from sorting by end, and the choice becomes dynamic programming.
The template
Write this from memory. Every problem in this section is a specialisation of it, and most bugs come from deviating without a reason.
intervals.sort() # by start
merged = []
for s, e in intervals:
if merged and s <= merged[-1][1]: # overlaps the current block
merged[-1][1] = max(merged[-1][1], e)
else: # a gap: start a new block
merged.append([s, e])std::sort(iv.begin(), iv.end()); // by start
std::vector<std::vector<int>> merged;
for (const auto& x : iv) {
if (!merged.empty() && x[0] <= merged.back()[1])
merged.back()[1] = std::max(merged.back()[1], x[1]); // overlaps: extend
else
merged.push_back(x); // a gap: new block
}Arrays.sort(iv, (a, b) -> Integer.compare(a[0], b[0])); // by start
List<int[]> merged = new ArrayList<>();
for (int[] x : iv) {
int[] last = merged.isEmpty() ? null : merged.get(merged.size() - 1);
if (last != null && x[0] <= last[1]) last[1] = Math.max(last[1], x[1]); // overlaps: extend
else merged.add(new int[]{x[0], x[1]}); // a gap: new block
}intervals.sort((a, b) => a[0] - b[0]); // by start
const merged = [];
for (const [s, e] of intervals) {
const last = merged[merged.length - 1];
if (last && s <= last[1]) last[1] = Math.max(last[1], e); // overlaps: extend
else merged.push([s, e]); // a gap: new block
}iv.sort(); // by start
let mut merged: Vec<[i64; 2]> = Vec::new();
for &[s, e] in &iv {
match merged.last_mut() {
Some(last) if s <= last[1] => last[1] = last[1].max(e), // overlaps: extend
_ => merged.push([s, e]), // a gap: new block
}
}import Data.List (sort)
-- Sort by start; fold each range into the last block or start a new one.
merge :: [(Int, Int)] -> [(Int, Int)]
merge = reverse . foldl step [] . sort
where
step ((s0, e0) : acc) (s, e) | s <= e0 = (s0, max e0 e) : acc
step acc x = x : acc
Problems
Total time covered by a set of bookings
Overlapping bookings must not be counted twice. Sort by start time and one pass merges them into blocks.
Open the problem → Medium18 minFewest meeting rooms for a day of meetings
The number of rooms needed is the most meetings that are ever running at once. Sort the starts and the ends and walk through them together.
Open the problem → Medium18 minFewest bookings to cancel so none overlap
Keeping the most bookings means removing the fewest. Sort by end time and keep every booking that fits.
Open the problem → Hard30 minMost profit from jobs that do not overlap
Each job has a value, so ending earliest is no longer safe. Sort by end time; each job either joins the best schedule ending before it starts, or is skipped.
Open the problem →Or sit a timed interview: a fixed window, limited submissions, and a report at the end.