Skip to content
QSWEQB
easyCodingEntryMidSenior

Merge a list of overlapping intervals, and justify the key you sorted by

Sorting by start time is what reduces the merge to a single scan carrying one scalar, the end of the open block; sorting by end breaks it, and sorting by end is instead correct for maximum non-overlapping selection. Both run in O(n log n), and equal timestamps decide the answer.

4 min readUpdated 2026-07-26

What the interviewer is scoring

  • Whether the sort key is derived from the state the scan must carry, rather than asserted as the standard approach
  • Does the candidate take the maximum of the two ends when merging, or assume intervals are never nested
  • That closed versus half-open semantics are raised before the code is written, not after a failing case
  • Whether they can name a different interval problem where a different sort key is correct, and say why
  • Does the candidate reach for a sweep line when the question moves from merging to counting concurrency

Answer

What the sort is buying

Overlap is a pairwise relation, so the naive formulation compares every interval against every other and costs O(n²). Sorting by start time collapses that. After sorting, when the scan reaches interval i, every interval with an earlier start has already been consumed, and the only fact about all of them that can still matter is the furthest right edge of the block currently open. Everything else has been summarised away. That is the justification, and it is a claim about state: sorting by start reduces the history you must carry from a set of intervals to a single integer.

Sorting by end does not have that property, and the counterexample is short. Take [0,2], [5,6], [1,10]. By ascending end that is exactly the order given. The scan opens [0,2], sees [5,6] starting after 2, closes and emits [0,2], then sees [1,10] which overlaps the block it just emitted. The failure is structural: with ends ascending, a later interval can still begin earlier than anything you have seen, so no single scalar summarises the past.

Arrays.sort(iv, Comparator.comparingInt(a -> a[0]));

List<int[]> out = new ArrayList<>();
int[] cur = iv[0].clone();
for (int i = 1; i < iv.length; i++) {
    if (iv[i][0] <= cur[1]) {                        // closed intervals: touching merges
        cur[1] = Math.max(cur[1], iv[i][1]);         // max, because [1,10] then [2,3] is nested
    } else {
        out.add(cur);
        cur = iv[i].clone();
    }
}
out.add(cur);                                        // the last block is never closed by a successor

Two lines in there are where implementations die. Math.max rather than a plain assignment is the nested-interval case, and it survives most hand-written test data because people write examples that overlap partially. Appending cur after the loop is the other, since nothing inside the loop ever emits the final block.

Cost is O(n log n) for the sort plus a linear pass, with O(n) for the output and O(1) working state. If the caller can tolerate mutation and the array is already start-sorted, the merge is genuinely in place.

Sorting by end, and when that is the right key

The reason to insist the sort key is derived rather than remembered is that a neighbouring problem wants the opposite key. Selecting the largest set of pairwise non-overlapping intervals is greedy on earliest end time, because finishing early is what leaves the most room for everything after it, and that greedy choice survives an exchange argument. Merging wants earliest start; scheduling wants earliest finish. Same input type, different key, and the key follows from what the algorithm needs to be true at each step.

Counting concurrency with a sweep

Change the question to "how many meeting rooms do you need" and you no longer want merged blocks — you want the maximum number of intervals live at any instant. Separate the endpoints, sort each list, and sweep them together, treating an end before a start when times coincide.

int[] starts = ..., ends = ...;                      // both sorted ascending, length n
int rooms = 0, best = 0, e = 0;
for (int s = 0; s < n; s++) {
    while (ends[e] <= starts[s]) { rooms--; e++; }   // a room freed at t is reusable at t
    rooms++;
    best = Math.max(best, rooms);
}

The end pointer needs no bounds check, and the reason is a small counting argument worth having ready: if e ever reached s + 1 there would be s + 1 intervals whose end is at or before starts[s], hence s + 1 intervals whose start is strictly below starts[s], which is impossible when starts[s] is the (s+1)-th smallest start. Two sorts and two linear pointers gives O(n log n) time and O(n) space.

Equal timestamps are not a detail

The <= in the merge and the <= in the sweep are both semantic decisions that the problem statement usually leaves implicit, and they change the output. On closed intervals, [1,2] and [2,3] share the instant 2, so they merge into [1,3] and they need two rooms. On half-open [1,2) and [2,3) they are disjoint, so they stay separate and one room suffices. Nothing in the data tells you which convention applies; the domain does. Meeting times and calendar slots are almost always half-open, while inclusive integer ranges like line numbers or version ranges are usually closed.

Ask, or state your assumption and move on — either is a strong signal, and silently picking one is the thing that gets marked down. Under an event-list formulation the same decision reappears as a tie-break rule in the comparator, ordering end events before start events at identical times, which is easy to get wrong precisely because it looks like a stylistic choice rather than a correctness one.

Name the sort key as a consequence of the state your scan carries, and settle closed versus half-open before writing the first comparison. Both are places where the code looks equally reasonable either way and only one answer is right.

Likely follow-ups

  • How would you insert a single new interval into an already-merged list without re-sorting?
  • The input arrives as a stream too large to sort. What changes?
  • Given employee calendars, find the free slots common to all of them. What is the cost?
  • Why does maximum non-overlapping selection sort by end time when merging sorts by start?

Related questions

intervalssweep-linesortingschedulinginvariants