33

Intervals

Sort them the right way, and one sweep answers merge, schedule and room-count questions.

intermediate~65 min5 simulations10 problems

▶ guided lesson⚡ 1-minute summary↻ 14 recall cards

before this: Sorting · Heaps · Greedy

01 · why it exists

Why this exists, and the weight it carries

since?

Interval merging (sort by start)

A folklore technique with no single inventor: it's what any "union of ranges" code does after sorting. The HTML standard even requires browsers to keep a video's buffered ranges merged this way.

its jobs today

  • Browsers: a media element's buffered and seekable ranges must be normalized: sorted, not overlapping and not touching (HTML standard).
  • Calendars and booking systems: merge busy blocks to show free slots (Employee Free Time).
  • Genomics: bedtools merge collapses overlapping genome intervals before coverage is counted.

the weight it carries

Illustrative: 10⁴ busy blocks from a team calendar. Merging any overlapping pair again and again: up to n merges × n² pair checks ≈ 10¹² steps. Sort + one sweep: about 10⁴ × log₂ 10⁴ ≈ 1.3 × 10⁵ comparisons + 10⁴ steps.

without it

Overlapping ranges get double-counted (coverage looks bigger than it is), and every free-slot search compares every pair.

when · why · how

when
You need the union of ranges, or you insert into / search a set of disjoint ranges.
why
After sorting by start, only the last group can overlap the next interval: O(n log n) in total instead of O(n²) pair checks.
how
Arrays.sort(iv, (a, b) -> Integer.compare(a[0], b[0])), a List<int[]> merged, and last[1] = Math.max(last[1], cur[1]).

02 · plain english

The idea in 30 seconds

An interval is a stretch [start, end]: a meeting, a booking, a downloaded piece of a video, a balloon on the x-axis.

Almost every interval problem starts with a sort: by start to merge or sweep, by end to keep the most or to stab them with the fewest points.

After the sort, one left-to-right pass, with a variable or a min-heap of end times, gives the answer in O(n log n).

picture this

The grey buffered bar under a video. As you skip around, the browser downloads pieces as [start, end] time ranges and keeps them merged: the HTML standard says the buffered ranges must be sorted, never overlap and never touch, so touching pieces are folded into one. That is Merge Intervals, running inside every browser's video player.

03 · remember

What you must know

  • Overlap test for closed [a, b] and [c, d]: a <= d && c <= b. For half-open meetings [a, b): a < d && c < b. One < vs <= decides whether touching counts.
  • Read the touching rule every time. LC 56 merges [1,4] and [4,5]. LC 435 and meeting rooms let [1,2] and [2,3] live together. In LC 452 one arrow at x = 2 bursts both [1,2] and [2,3].
  • Sort by start to combine or sweep (merge, insert, meeting rooms, free time). After the sort, the next interval can only overlap the last group.
  • Sort by end to keep the most intervals or to stab them with the fewest points (LC 435, LC 452). The earliest finisher leaves the most room: the exchange argument from the Greedy chapter.
  • Rooms needed = the most intervals overlapping at one moment. Get it with a min-heap of end times, or with two sorted arrays (starts and ends).
  • Sweep line / difference array: +k at a start, −k at an end, then a running sum. An int[] for small coordinates, a TreeMap for huge or sparse ones.
  • Offline queries: when many points ask about many intervals (LC 1851), sort the queries too and sweep both together with a heap.
  • Comparator safety: Integer.compare(a[0], b[0]), never a[0] - b[0]. LC 452's coordinates span the whole int range.

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Merge intervals

After sorting by start, each interval either stretches the current group or opens a new one.

loading simulation…
sim

Insert interval: copy, absorb, copy

The list is already sorted, so no sort: copy what ends before, absorb what overlaps, copy what starts after.

loading simulation…
sim

Activity selection on a timeline

Sorted by end time. Each activity is kept or skipped by one comparison with `lastEnd`.

loading simulation…
sim

Meeting rooms with a min-heap of end times

The heap's top is the room that frees up first. If it's free by the next start, reuse it.

loading simulation…
sim

Sweep line: how many rooms?

Starts and ends sorted on their own. A start before the earliest unused end means a new room.

loading simulation…

05 · cost

Time & space, explained

Keep the most non-overlapping intervals: three ways
TimeExtra spacewhy
Try every subsetO(2ⁿ · n)O(n)2ⁿ subsets, each checked in O(n) after sorting. Hopeless past n ≈ 25.
DP over intervals sorted by startO(n²)O(n)For each interval, look back at every earlier one (an LIS-style DP).
Greedy: sort by end + one passO(n log n)O(1)–O(n)The sort dominates. The pass is O(n). Space is whatever the sort needs.
Interval toolkit
TimeExtra spacewhy
Sort intervals (Arrays.sort(int[][], cmp))O(n log n)O(n)Rows are objects, so Java uses its stable merge sort (TimSort), which may need up to n/2 temporary slots.
Merge overlapping intervalsO(n log n)O(n)Sort by start, then one pass. The output can hold n intervals.
Insert into a sorted, disjoint listO(n)O(n)Already sorted, so no sort: copy before, absorb overlaps, copy after.
Max non-overlapping (activity selection)O(n log n)O(1)–O(n)Sort by end, then one comparison per interval.
Max overlap: min-heap of end timesO(n log n)O(n)Sort, then each meeting does at most one poll and one offer at O(log n) each.
Max overlap: sweep over sorted starts/endsO(n log n)O(n)Two sorts, then one linear merge-like walk.
Max overlap: difference array over range RO(n + R)O(R)Two writes per interval, then one prefix-sum sweep over R slots. Best when R is small.
Free time of k people, N intervals in totalO(N log k)O(k)A heap holds each person's next interval: N polls and offers on a heap of size k.
Smallest interval per query (n intervals, q queries)O(n log n + q log q)O(n + q)Sort both lists; each interval enters and leaves the heap once.

06 · java

Raw vs Collections Framework

The overlap test: closed vs half-open

javatwo intervals, one line eachjava.util.*
class Overlap {    /** Closed intervals [s, e]: touching ends ([1,4] and [4,5]) share the point 4, so they overlap. */    static boolean closed(int[] a, int[] b) {        return a[0] <= b[1] && b[0] <= a[1];    }     /** Half-open [s, e) like meetings: [1,4) and [4,5) only touch, so they don't overlap. */    static boolean halfOpen(int[] a, int[] b) {        return a[0] < b[1] && b[0] < a[1];    }     /** The shared part of two closed intervals, or null if they don't meet. */    static int[] intersection(int[] a, int[] b) {        int lo = Math.max(a[0], b[0]), hi = Math.min(a[1], b[1]);        return lo <= hi ? new int[] {lo, hi} : null;    }}
  • Two intervals overlap when each one starts before the other ends. Test that, never the four "cases" of how they can sit.
  • Closed [s, e] (merging, balloons): <=. Half-open [s, e) (meetings, bookings): <.
  • The shared part is [max(starts), min(ends)], and it exists only when that start ≤ that end.

Sorting intervals: int[][] vs a record

javaint[][] + lambdajava.util.*
class SortRaw {    static void byStart(int[][] intervals) {        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));    }     static void byEnd(int[][] intervals) {        // Never write (a, b) -> a[1] - b[1]: it overflows for values near ±2^31.        Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));    }}
  • LeetCode hands you int[][]. Sort it with Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])).
  • Never write (a, b) -> a[0] - b[0] when values can be near ±2³¹. The subtraction overflows and the sort silently goes wrong. LC 452 is built to catch exactly this.
  • In real code, an Interval record with Comparator.comparingInt(...).thenComparingInt(...) reads better and can't mix up start and end.
  • Sorting int[][] is sorting objects (each row is an array), so Java uses a stable merge sort (TimSort), not the dual-pivot quicksort it uses for int[].

Keep the most: activity selection

javasort by end, keep what fitsjava.util.*
class ActivitySelection {    /** Most intervals [start, end) you can keep with no two overlapping. Touching ends are fine. */    static int maxActivities(int[][] intervals) {        int[][] sorted = intervals.clone();        Arrays.sort(sorted, (a, b) -> Integer.compare(a[1], b[1])); // earliest END first        int count = 0, lastEnd = Integer.MIN_VALUE;        for (int[] iv : sorted) {            if (iv[0] < lastEnd) continue; // overlaps the last pick, so skip it            count++;                        // fits, so take it            lastEnd = iv[1];        }        return count;    }}
  • Sort by the key that makes the greedy choice obvious, keep one variable of state (lastEnd), make one decision per interval.
  • Integer.MIN_VALUE as the first lastEnd means "nothing kept yet", so the first interval always fits.
  • iv[0] < lastEnd treats touching intervals ([1,2] and [2,3]) as compatible. Read the problem to see whether touching counts as overlap.

Counting overlaps: sorted arrays vs PriorityQueue

javasweep line (two int[])java.util.*
class Solution {    public int minMeetingRooms(int[][] intervals) {        int n = intervals.length;        int[] starts = new int[n], ends = new int[n];        for (int i = 0; i < n; i++) {            starts[i] = intervals[i][0];            ends[i] = intervals[i][1];        }        Arrays.sort(starts);        Arrays.sort(ends);        int rooms = 0, e = 0;        for (int s = 0; s < n; s++) {            if (starts[s] < ends[e]) { // nobody has left yet                rooms++; // open a new room            } else {                e++; // someone left: take over their room            }        }        return rooms;    }}
  • PriorityQueue<Integer> is a min-heap by default. peek() is O(1); offer and poll are O(log n).
  • The heap version also tells you which room frees up first. That matters for follow-ups like LC 2402 Meeting Rooms III.
  • The sweep version uses primitive int[]: no boxing and very cache-friendly. It is often faster in practice.

Range counting: difference array vs TreeMap

javadifference arrayjava.util.*
class Solution {    public boolean carPooling(int[][] trips, int capacity) {        int[] change = new int[1001];                      // difference array over the road        for (int[] t : trips) {            change[t[1]] += t[0];                          // they get in at "from"            change[t[2]] -= t[0];                          // and out at "to"        }        int inCar = 0;        for (int km = 0; km <= 1000; km++) {               // sweep left to right            inCar += change[km];            if (inCar > capacity) return false;        }        return true;    }}
  • Small coordinates (≤ 10⁶)? Use a plain int[] difference array: O(n + range), no sorting.
  • Huge or sparse coordinates (up to 10⁹)? A TreeMap<Integer, Integer> keeps only the points where something changes. merge(key, delta, Integer::sum) adds to it in one line.
  • Iterating a TreeMap's values() visits keys in increasing order, which is exactly the sweep.
Java API you will actually use
calldoescost
Arrays.sort(int[][] a, Comparator)Stable sort of the rows (TimSort)O(n log n)
Integer.compare(x, y)Overflow-safe comparison for comparatorsO(1)
Comparator.comparingInt(f).thenComparingInt(g)Sort by one key, break ties by anotherO(1) per compare
new PriorityQueue<Integer>()Min-heap (use Comparator.reverseOrder() for max)O(1) to build
pq.offer(x) / pq.poll() / pq.peek()Insert / remove the smallest / look at itO(log n) / O(log n) / O(1)
treeMap.merge(k, v, Integer::sum)Add a +/− event at point kO(log n)
treeMap.ceilingEntry(x)Smallest key ≥ x (or null)O(log n)
Integer[] order + Arrays.sort(order, cmp)Sort query positions by value, keeping the original orderO(q log q)
list.toArray(new int[0][])Turn a List<int[]> into the int[][] LeetCode expectsO(n)

Merge or insert intervals

→ Sort by start + List<int[]>

Overlapping intervals end up next to each other.

Keep the most non-overlapping intervals / fewest removals / fewest arrows

→ Sort by end + lastEnd

The earliest finisher is always a safe pick (exchange argument).

Max number of simultaneous intervals

→ Min-heap of ends, or a sweep over sorted starts/ends

Both O(n log n). The heap also tells you which resource frees up first.

Overlap counts on a small coordinate range

→ int[] difference array

O(n + range) with no sorting.

Overlap counts on huge or sparse coordinates

→ TreeMap of +/− events

Stores only the points where something changes.

Common free time of several people

→ Merge everyone's busy intervals (or a heap of each person's next one)

Free time is just the gaps between merged busy blocks.

Many point queries over many intervals

→ Sort queries + intervals, sweep with a heap

Each interval enters and leaves the heap once.

07 · recognise

Spot the pattern

"Merge", "insert", "union of ranges"

Sort by start, extend the last group

"Maximum number of non-overlapping …", "minimum removals so nothing overlaps"

Sort by end, keep what fits (activity selection)

"Minimum rooms / platforms / cars", "max concurrent", "capacity at any time"

Min-heap of end times, sweep line, or difference array

"Minimum arrows / points to cover all intervals"

Sort by end, place the point at the earliest end

"When is everyone free?"

Merge all busy intervals; the gaps are the answer

"For each query point, the best interval containing it"

Sort the queries too (offline), sweep with a heap

Sorted unique numbers to describe as ranges

Stretch each run while the next number is one more

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Sorting by the wrong key. Sorting by start (or by length) breaks activity selection: [1,10], [2,3], [4,5].
  • Comparator overflow: a[1] - b[1] breaks for values near ±2³¹. Use Integer.compare.
  • Getting "touching" wrong. LC 56 merges [1,4] and [4,5]. LC 435 allows [1,2] and [2,3] together. LC 452 bursts both [1,2] and [2,3] with one arrow at 2.
  • Merge: writing last[1] = iv[1] instead of Math.max(last[1], iv[1]). An interval fully inside the group would shrink it.
  • Meeting Rooms II: peek() < start instead of <= gives back-to-back meetings two rooms.
  • Car Pooling: subtracting at to + 1. Passengers leave at to, so the seat is free for someone boarding there.
  • LC 1851: sorting the queries array itself and losing where each answer belongs.
  • Mutating the caller's array when sorting. Clone it first if the input must stay unchanged.

tricks

  • Draw the intervals as bars on a number line. Overlaps and the right sort order jump out.
  • Ask two questions before coding: which key do I sort by, and do touching intervals overlap in this problem?
  • Edge cases to test: one interval, identical intervals, nested intervals, touching ends, negative or extreme coordinates.
  • For "how many at once" questions, mention both the heap and the sweep, then code the one you're faster at.
  • Chalo, dry-run karte hain: run your loop on three intervals by hand before typing. Most interval bugs are one < or one missing max.

09 · why & how

Questions you might be too shy to ask

Should I sort by start or by end?

Sort by end when you're choosing intervals to keep, like activity selection, LC 435 and LC 452. The earliest end leaves the most room. Sort by start when you're combining or sweeping, like merge, insert, meeting rooms and free time. Overlapping intervals then sit next to each other.

Why not pick the shortest interval first?

Shortest-first can block two others. Take [1,5], [4,7], [6,10]. The shortest is [4,7] (length 3), and it overlaps both of the others, so you keep only 1. Earliest-end keeps [1,5] and [6,10], which is 2. Earliest-start fails too: [1,10], [2,3], [4,5].

Do touching intervals overlap?

It depends on the problem, so read it carefully. Merge Intervals (LC 56) treats [1,4] and [4,5] as overlapping. Non-overlapping Intervals (LC 435) treats [1,2] and [2,3] as fine together. Meeting rooms treat [5,10] and [10,15] as able to share a room. Balloons (LC 452) are closed, so one arrow at x = 2 bursts both [1,2] and [2,3]. One < vs <= decides it.

Merge uses iv[0] > last[1] but LC 435 uses iv[0] < lastEnd. Why does one say > and the other <?

They ask opposite questions. Merge asks "is there a gap?": a new group starts only when the start is strictly after the end, so touching [1,4] and [4,5] merge. LC 435 asks "is there a clash?": a clash only when the start is strictly before the last end, so touching [1,2] and [2,3] are fine. Dhyan do: decide the touching rule first, then the sign writes itself.

I wrote last[1] = ... but never put last back into the list. How does merged change?

An int[] is an object. merged.get(...) hands you a reference to the same array that sits inside the list, not a copy. So last[1] = 7 edits the array the list already holds. Nothing to put back. That's also why we add new int[] {iv[0], iv[1]}: a fresh copy, so stretching a group never edits the caller's input row.

What is new int[0][] in merged.toArray(new int[0][])?

It only tells Java the type you want back: an int[][]. The empty array is too small, so toArray creates a new one of exactly the right size and fills it. Without the argument, toArray() returns Object[], which you can't return as int[][].

My intervals are objects like Interval with start and end, not int[][]. How do I sort them?

Same idea, different syntax. For a List<Interval>: list.sort(Comparator.comparingInt(iv -> iv.start)). For an Interval[]: Arrays.sort(arr, Comparator.comparingInt(iv -> iv.start)). Employee Free Time hands you objects exactly like this.

For Insert Interval, can't I just add the new one and call my merge code?

Yes, and it's correct: add it, sort, merge. That's O(n log n). But the list is already sorted and disjoint, so the copy–absorb–copy walk does it in O(n) with no sort. Say the simple version first if you like, then give the O(n) one — that is what the interviewer is waiting for.

Heap or sweep line for Meeting Rooms II?

Both are O(n log n) and both are accepted. The sweep line is shorter and uses primitive arrays. The heap matches the story ("rooms with end times") and extends to follow-ups that ask which room to use. Know one well and mention the other.

Why does the Meeting Rooms II heap hold end times, not start times?

The only question at each new meeting is "is some room free by now?". A room becomes free at its meeting's end. The smallest end in the heap is the room that frees up first, so one peek() answers it. Start times of running meetings don't help decide anything.

In the heap version, why if and one poll(), not a while loop that frees every finished room?

The new meeting needs only one room, so freeing one free room is enough. With if, the heap never shrinks: its size only grows when no room was free. So ends.size() at the end is the peak number of rooms. A while loop is also correct, but then the heap can shrink, and you must track max(size) yourself.

What exactly is a "sweep line"?

Picture a vertical line moving left to right over the number line. It only stops at events (starts and ends), in sorted order, and keeps a running state such as "how many intervals are open". Nothing changes between two events, so you skip that stretch. Sorting the events costs O(n log n); the walk is O(n).

LC 1851 answers the queries in a different order. Isn't that cheating?

No. It's called answering offline: all queries are known up front, so you may handle them in any order, as long as each answer goes back to its original position. That's why we sort an array of indices, not the queries themselves.

The interviewer gives arrival[] and departure[] as two separate arrays (minimum railway platforms). What changes?

Nothing important. It's Meeting Rooms II with trains. Sort both arrays on their own and run the sweep: an arrival before the earliest unused departure needs a new platform. Splitting the pairs is fine because you only count, you never ask which train uses which platform. Ask one thing first: can a train arrive at the same minute another one leaves and use that platform?

Meeting Rooms and Employee Free Time are LeetCode Premium. How do I practise them?

Right here: the statement, the code runner and the judge on this page are free. The linked LintCode twins practise the same idea; their input format differs a little (Interval objects, or flattened arrays).

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #228
    Summary Ranges
    EasyNetflixGoogleMeta
  2. #252
    Meeting RoomsLeetCode Premium
    EasyMetaAmazonGoogleBloomberg
  3. #56
    Merge Intervalsmust-do
    MediumWalmartBloombergAdobeMetaApple+1
  4. #57
    Insert Intervalmust-do
    MediumLinkedInGoogleMicrosoftMetaAmazon
  5. #435
    Non-overlapping Intervalsmust-do
    MediumAmazonBloombergGoogleMeta
  6. #452
  7. #253
    Meeting Rooms IImust-doLeetCode Premium
    MediumNetflixByteDance / TikTokAtlassianOracleBloomberg+1
  8. #1094
    Car Pooling
    MediumMetaAmazon
  9. #759
    Employee Free TimeLeetCode Premium
    HardAirbnbGoogle
  10. #1851

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #729
    My Calendar I
    Mediumin BST

    Intervals arriving one by one: a TreeMap's floor and ceiling neighbours give the overlap test in O(log n).

  2. #853
    Car Fleet
    Mediumin Stacks

    Sort by position, then one sweep: the same sort-then-sweep habit.

  3. #23
    Merge k Sorted Lists
    Hardin Heaps

    The heap behind Employee Free Time's O(N log k) version.

11 · revise

The whole chapter on one screen

9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    An interval is [start, end]. Sort first (by start to merge or sweep, by end to keep the most), then one pass decides each interval once.

  2. use it when

    "Merge", "insert", "overlap", "rooms needed", "free time", "remove the fewest so none overlap", "capacity along a route".

  3. template

    Sort by start; if cur[0] > last[1] open a new group, else last[1] = Math.max(last[1], cur[1]).

  4. template

    Rooms: min-heap of ends. if (!pq.isEmpty() && pq.peek() <= s) pq.poll(); pq.offer(e); The heap's size is the answer.

  5. cost

    O(n log n) for the sort, O(n) for the sweep, O(log n) per heap step. A difference array is O(n + range).

  6. trap

    Touching rules differ: [1,4]+[4,5] merge in LC 56, but [1,2] and [2,3] live together in LC 435 and meeting rooms. One < vs <=.

  7. trap

    (a, b) -> a[1] - b[1] overflows near ±2³¹ and silently mis-sorts (LC 452). Use Integer.compare.

  8. trap

    Merging with last[1] = cur[1] shrinks the group on nested intervals like [1,10], [2,3]. Always take the max.

  9. pro tip

    Draw the intervals as bars on a number line first. The sort key and the overlap rule jump out.

12 · recall

Flip, remember, repeat

card 1 / 14
recall

Write the overlap test for closed [a,b] and [c,d], and for half-open meetings.

tap or Space to flip ↻

Question 1: Write the overlap test for closed [a,b] and [c,d], and for half-open meetings.

13 · check yourself

Quick quiz

  1. To keep the MOST non-overlapping intervals, which sort order makes greedy correct?

  2. Merge Intervals: after sorting by start, the current interval is [5, 7] and the last group is [1, 6]. What happens?

  3. Meetings [5, 10) and [10, 15). Can one person attend both?

  4. Insert [4, 8] into [[1,2],[3,5],[6,7],[8,10],[12,16]]. Which intervals get absorbed?

  5. Meeting Rooms II (heap): the heap holds ends {10, 15}. The next meeting is [10, 12]. What does the heap look like afterwards?

  6. Why is `(a, b) -> a[1] - b[1]` dangerous in LC 452?

  7. LC 1851: why can an interval popped from the heap (right < q) be thrown away for good?