Intervals and Sweep Line
Sort intervals by start, then merge, insert or count overlaps in a single sweep.
- O(n log n)Time: the sort dominates the sweep
- O(n)Space: output, events or heap
- -1 before +1Order for events at the same time
Show what to say
“When I see [start, end] pairs and a question about overlap, I sort first: by start to merge or find clashes, by end to keep the most. Then one sweep answers it, and +1/-1 events or a min-heap of end times count the rooms. That is O(n log n) instead of comparing every pair.”
Trade-offs
- Sweep line vs min-heap: both O(n log n); the sweep line is shorter for how many rooms, the heap suits assigning rooms or streams.
- Sort by start vs by end: start merges and finds clashes; end keeps the most, since [1, 100] first would block everything.
- Sorting vs difference array: small integer coordinates allow +1 at start, -1 at end and one prefix sum, with no O(n log n) sort.
Bring it up before they ask
Settle whether touching intervals overlap before coding ([1, 3] and [3, 5] clash with <= but not with <), and extend with Math.max because a later interval can sit inside an earlier one.
Sort the intervals and most problems become one sweep. To count overlaps, sweep +1/-1 events or keep a min-heap of end times: O(n log n) instead of O(n²).
When to reach for it
Tap the signals you can see in the problem.
Remember`[start, end]` pairsoverlaprooms
- Merge, insert or clash check
Sort bystart
merge intervalsmeeting rooms - Count overlaps
Sort byevent time, or start plus a heap
meeting rooms II - Keep the most
Sort byend
non-overlapping intervals - Two sorted lists
Sort byalready sorted
interval list intersections
The core idea: sorting makes overlap local
Picture meetings as bars on a calendar.
Read more · 61 words
Two overlap when each starts before the other ends. A meeting ending at 3 and one starting at 3 do not clash.
Checking every pair is O(n²). Sort by start and only neighbours matter: once an interval starts after the current group ends, every later one does too, so the group is final. Try your own intervals with Edit input:
Sort by start, then sweep: merging intervals
In input order, intervals that overlap can be far apart ([7,8] comes first). Sort by start so overlapping intervals end up next to each other.
function merge(intervals) {
intervals.sort((a, b) => a[0] - b[0]);
const merged = [];
for (const [start, end] of intervals) {
const last = merged.at(-1);
if (last && start <= last[1]) {
last[1] = Math.max(last[1], end);
} else {
merged.push([start, end]);
}
}
return merged;
}All steps as text
- In input order, intervals that overlap can be far apart ([7,8] comes first). Sort by start so overlapping intervals end up next to each other.
- Sorted. Now sweep from left to right. The last interval in merged is the group still being built; everything before it is final.
- merged is empty, so [1,3] starts the first group.
- Start a new group with [1,3].
- [2,4] starts at 2, which is not after the open group's end (3), so they overlap.
- Stretch the group's end to 4: it now covers everything from 1 to 4.
- [4,5] starts at 4, which is not after the open group's end (4), so they overlap.
- Stretch the group's end to 5: it now covers everything from 1 to 5.
- [7,8] starts at 7, after the open group ends at 5. Every later interval starts even further right, so [1,5] is final.
- Start a new group with [7,8].
- [10,11] starts at 10, after the open group ends at 8. Every later interval starts even further right, so [7,8] is final.
- Start a new group with [10,11].
- One pass after the sort: 5 intervals merged into 3: [1,5], [7,8], [10,11].
Play with it
Drag meetings around the day. Press Run to sweep a line across it: the peak is the number of rooms.
Read more · 81 words
Pin down the overlap rule
Do
[1, 3]and[3, 5]overlap? Decide before any loop.Choose the sort key
Start to merge or find clashes, end to keep the most.
Decide what the sweep remembers
The open group, a count, or a heap of end times.
Process each interval in one step
Extend with
Math.max: a later interval can sit inside an earlier one.Flush and check the edges
Push the last open group. Test nested and back-to-back intervals.
Templates
Template 1: sort by start, then sweep
Sort a copy by start, keep the last merged interval open, then stretch it or start a new one.
Read more · 32 words
This is Merge IntervalsMedium. With nothing to merge it answers Meeting RoomsEasy: after sorting, a clash shows between neighbours. For Insert IntervalMedium the list is already sorted, so skip the sort and walk it once.
// Template 1: sort by start, then sweep. The last interval in `merged` is the group still being built;
// an interval that starts after its end can never be reached by anything later, so the group is final.
export function mergeIntervals(intervals) {
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
const merged = [];
for (const [start, end] of sorted) {
const last = merged.at(-1);
if (last && start <= last[1]) last[1] = Math.max(last[1], end); // overlaps (or touches): stretch
else merged.push([start, end]); // a gap: start a new group
}
return merged;
}
// Total length covered by at least one interval: merge first, then nothing is counted twice.
export function coveredLength(intervals) {
return mergeIntervals(intervals).reduce((sum, [s, e]) => sum + (e - s), 0);
}Template 2: count how many overlap at once
"How many rooms?" means "how many meetings run at the busiest moment?"
Read more · 64 words
That count only changes at a start (+1) or an end (−1). Sort these events by time and keep a running sum: the sweep line. On ties put −1 first.
To know which room each meeting gets, take meetings in start order with a min-heap of end times. Reuse the room that frees first if it's free by the next start; otherwise open one.
Sweep line: the most intervals open at once
Turn each meeting into two events: +1 when it starts, -1 when it ends. The meetings themselves no longer matter, only these moments.
function maxOverlap(intervals) {
const events = [];
for (const [s, e] of intervals) events.push([s, +1], [e, -1]);
// by time; at equal times the -1 (an end) goes first
events.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
let open = 0, most = 0;
for (const [time, delta] of events) {
open += delta;
most = Math.max(most, open);
}
return most;
}All steps as text
- Turn each meeting into two events: +1 when it starts, -1 when it ends. The meetings themselves no longer matter, only these moments.
- Sort the events by time. When an end and a start share a time, the end (-1) goes first: a room freed at that moment can be reused at once.
- Sweep from left to right. open is how many meetings are running right now; most is the highest it has been.
- At time 1 a meeting starts: open = 1. That is a new high, so most = 1. The highlighted column shows the meetings running at that moment.
- At time 2 a meeting starts: open = 2. That is a new high, so most = 2.
- At time 4 a meeting starts: open = 3. That is a new high, so most = 3.
- At time 5 a meeting ends: open drops to 2.
- At time 6 a meeting ends: open drops to 1. It is handled before the start at the same time, so the two never count as overlapping.
- At time 6 a meeting starts: open = 2, not above the best so far (3).
- At time 8 a meeting ends: open drops to 1. It is handled before the start at the same time, so the two never count as overlapping.
- At time 8 a meeting starts: open = 2, not above the best so far (3).
- At time 9 a meeting ends: open drops to 1.
- At time 10 a meeting ends: open drops to 0.
- Every event is processed and open is back to 0. At most 3 meetings ran at the same time (first reached at time 4), so 3 rooms are enough.
// Template 2a: sweep line. Every interval [start, end) becomes +1 at its start and -1 at its end.
// Sorted by time (ends before starts on ties), the running sum is how many intervals are open right now.
export function maxOverlap(intervals) {
const events = [];
for (const [start, end] of intervals) events.push([start, +1], [end, -1]);
events.sort((a, b) => a[0] - b[0] || a[1] - b[1]); // at equal times, -1 sorts before +1
let open = 0;
let most = 0;
for (const [, delta] of events) {
open += delta;
most = Math.max(most, open);
}
return most;
}Min-heap of end times: reuse the room that frees up first
Take the meetings in order of start time. The heap will hold one end time per room in use, smallest on top.
function minRooms(intervals) {
intervals.sort((a, b) => a[0] - b[0]);
const ends = new MinHeap();
for (const [start, end] of intervals) {
if (ends.size && ends.peek() <= start) {
ends.pop();
}
ends.push(end);
}
return ends.size;
}All steps as text
- Take the meetings in order of start time. The heap will hold one end time per room in use, smallest on top.
- Sorted by start, and no rooms are open yet.
- No room is open yet, so [0,5] gets the first one. Push 5, the time this meeting's room frees up. Rooms in use: 1.
- [1,3] starts at 1, but even the earliest-ending room is busy until 5. Every room is busy, so this meeting needs a new one.
- Push 3, the time this meeting's room frees up. Rooms in use: 2.
- [2,7] starts at 2, but even the earliest-ending room is busy until 3. Every room is busy, so this meeting needs a new one.
- Push 7, the time this meeting's room frees up. Rooms in use: 3.
- [4,6] starts at 4. The earliest-ending room frees up at 3, which is not after 4: that room is free.
- Pop 3: that room is handed over to the new meeting.
- Push 6, the time this meeting's room frees up. Rooms in use: 3.
- [6,9] starts at 6. The earliest-ending room frees up at 5, which is not after 6: that room is free.
- Pop 5: that room is handed over to the new meeting.
- Push 9, the time this meeting's room frees up. Rooms in use: 3.
- Every meeting has a room. The heap never had to grow past 3, so 3 rooms are enough. Its size is the answer.
// A small binary min-heap of numbers (JavaScript has no built-in priority queue).
export class MinHeap {
#a = [];
get size() { return this.#a.length; }
peek() { return this.#a[0]; }
push(x) {
const a = this.#a;
a.push(x);
for (let i = a.length - 1; i > 0; ) {
const p = (i - 1) >> 1;
if (a[p] <= a[i]) break;
[a[p], a[i]] = [a[i], a[p]];
i = p;
}
}
pop() {
const a = this.#a;
const top = a[0];
const last = a.pop();
if (a.length) {
a[0] = last;
for (let i = 0; ; ) {
const l = 2 * i + 1;
const r = l + 1;
let m = i;
if (l < a.length && a[l] < a[m]) m = l;
if (r < a.length && a[r] < a[m]) m = r;
if (m === i) break;
[a[m], a[i]] = [a[i], a[m]];
i = m;
}
}
return top;
}
}
// Template 2b: rooms needed for half-open meetings. Take meetings by start; the heap holds the end time of each
// busy room, so its top is the room that frees up first. Reuse it if it is free, otherwise open a new room.
export function minRooms(intervals) {
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
const ends = new MinHeap();
for (const [start, end] of sorted) {
if (ends.size && ends.peek() <= start) ends.pop(); // that room is free again: hand it over
ends.push(end);
}
return ends.size;
}Template 3: keep the most, remove the fewest
Sort by end and keep every interval that starts at or after the last kept end.
Read more · 43 words
Ending first leaves the most room (Greedy).
That is Non-overlapping IntervalsMedium. "Minimum arrows to burst balloons" is the same loop.
Template 4: two sorted lists
Both lists sorted? One pointer each. Record the intersection, then advance whichever interval ends first (Two Pointers). See Interval List IntersectionsMedium.
intervals.sort((a, b) => a[1] - b[1]); // earliest end first
let kept = 0, lastEnd = -Infinity;
for (const [start, end] of intervals) {
if (start >= lastEnd) { kept++; lastEnd = end; }
}
// fewest removals = intervals.length - keptTry it
// Edit the intervals and press Run. The functions are the templates above.
console.log(mergeIntervals([[7, 8], [2, 4], [10, 11], [1, 3], [4, 5]])); // [[1, 5], [7, 8], [10, 11]]
console.log(mergeIntervals([[1, 10], [2, 3]])); // [[1, 10]] (nested: keep the max end)
const meetings = [[1, 5], [2, 6], [4, 8], [6, 9], [8, 10]];
console.log(maxOverlap(meetings), minRooms(meetings)); // 3 3 (two ways, same answer)
console.log(maxOverlap([[1, 3], [3, 5]])); // 1 (back to back is fine)Complexity and JavaScript you'll use
Complexity
Read more · 78 words
| Variant | Time | Extra space |
|---|---|---|
| Merge, clash check, keep the most | O(n log n) | O(n) |
| Sweep line, or start order plus min-heap | O(n log n) | O(n) |
| Two sorted lists | O(n + m) | O(n + m) |
JavaScript you'll use
JavaScript refresher: Sorting and comparators Destructuring, spread and rest Heaps and priority queuesintervals.sort((a, b) => a[0] - b[0]); // by start; sort() alone compares "10,12" < "9,10"
const sorted = [...intervals].sort((a, b) => a[1] - b[1]); // by end, without reordering the input
events.sort((a, b) => a[0] - b[0] || a[1] - b[1]); // by time, then -1 before +1
for (const [start, end] of sorted) { /* … */ } // unpack each pair
const last = merged.at(-1); // the open group
last[1] = Math.max(last[1], end); // mutate it in place: it's the same arrayInterview questions
Meeting rooms: sweep line or min-heap?
Both O(n log n). The sweep line is shorter for "how many?"; the heap suits assigning rooms or streams.
Intervals arrive one at a time. What changes?
Keep merged intervals in a balanced search tree by start: O(log n) per insert.
How would you find free time common to several calendars?
Merge everyone's busy intervals, then report the gaps.
The coordinates are small integers. Is there something faster than sorting?
A difference array: +1 at the start, −1 at the end, then a prefix sum. See Prefix Sums.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Meeting RoomsDecide whether one person can attend every meeting in a list without two of them overlapping.Easy
- Merge IntervalsCombine every group of overlapping intervals into one and return the non-overlapping result.Medium
- Insert IntervalInsert a new interval into a sorted list of disjoint intervals, merging wherever it overlaps.Medium
- Non-overlapping IntervalsRemove as few intervals as possible so that none of the remaining ones overlap.Medium
- Meeting Rooms IIFind the fewest meeting rooms needed so that every meeting gets a room for its whole duration.Medium
- Interval List IntersectionsGiven two sorted lists of disjoint closed intervals, return every interval where they overlap.Medium
- Car PoolingDecide whether a car with limited seats can serve every trip, given where each group gets in and out.Medium
- Partition LabelsCut a string into as many pieces as possible so that every letter appears in only one piece, and return the piece sizes.Medium
In real systems
This pattern is not only for coding rounds. Here is where it does the work in a design interview.