Chapter 22: Intervals, Events and Sweep-Line Algorithms
22.01Intermediate5 topics13 min4 interactive animations

Interval modeling

Modelling intervals precisely: closed versus open and half-open endpoints, what overlap means, normalising input and sorting endpoints.

Learning goals

  • Choose between closed, open and half-open intervals and state what each implies for touching endpoints
  • Write the overlap test and the intersection of two intervals correctly
  • Normalise input into one convention before running an algorithm
  • Pick a sort key and tie-breaker that suits the algorithm

Prerequisites

Sorting algorithmsGreedy algorithms and exchange arguments

Almost every interval bug is an off-by-one at an endpoint. Deciding the convention first, and writing it down, removes most of them before any algorithm runs.

22.01.1

Closed vs open intervals

A **closed** interval [a, b] contains both endpoints; an **open** interval (a, b) contains neither. Two closed intervals that merely touch, such as [1, 4] and [4, 7], share the single point 4, so they overlap. Two open intervals (1, 4) and (4, 7) share nothing.

Run the first two presets of the animation on the same data, 1-4 and 4-7. Under the closed definition they overlap and the shared part is the single point [4, 4]; under the half-open definition they do not. The data did not change, only the meaning of the endpoints, and the answer flipped.

Closed intervals suit integer or discrete data (days 3 through 5 inclusive, line numbers). Open intervals mostly appear in mathematics; programs almost never need them.

Java
1static boolean overlapsClosed(int[] a, int[] b) {
2 return a[0] <= b[1] && b[0] <= a[1]; // touching endpoints share a point
3}
4
5static boolean overlapsOpen(int[] a, int[] b) {
6 return a[0] < b[1] && b[0] < a[1]; // touching endpoints share nothing
7}

Do two intervals overlap? Closed, half-open and open

Interactive · edit the input

Your input

Try

I1

[1, 4]

I2

[4, 7]
01234567
Setup

Two intervals overlap when they share at least one point (or, for the open conventions, a stretch of positive length). The convention for the endpoints decides what touching ends mean: closed intervals include both ends, so touching counts.

Step 1 / 4
Java
1static boolean overlaps(int a1, int b1, int a2, int b2, int kind) { // kind 0: closed, 1: half-open, 2: open
2 if (kind == 0) return a1 <= b2 && a2 <= b1; // closed: sharing a single point counts as overlap
3 return a1 < b2 && a2 < b1; // half-open and open: touching ends do not overlap
4}

Live state

convention

Closed [a, b]

No condition evaluated on this step
Execution trace (dry run table)Show 4 steps
StepLineStateWhat happens
1L1convention=Closed [a, b]Two intervals overlap when they share at least one point (or, for the open conventions, a stretch of positive length). The convention for the endpoints decides what touching ends mean: closed intervals include both ends, so touching counts.
2L2a1=1 b2=7The first interval starts before the second one ends.
3L2a2=4 b1=4The second interval starts before the first one ends.
4L2return=true intersection=[4, 4]They overlap; the shared part is [4, 4] (a single point).
22.01.2

Half-open intervals

A **half-open** interval [a, b) contains a but not b. It is the convention of most programming: substring(from, to), Arrays.copyOfRange(a, from, to), List.subList(from, to) and for (int i = from; i < to; i++) are all half-open. Its advantages are exact: the length is b - a, an empty interval is a == b, adjacent intervals [1, 4) and [4, 7) tile without a gap or overlap, and splitting [a, c) at b gives [a, b) and [b, c) with no endpoint counted twice.

Meeting schedules are naturally half-open: a meeting from 9:00 to 10:00 and another from 10:00 to 11:00 may share a room. That is why the “Touching: half-open does not” preset reports no overlap, and why the meeting-room algorithms in this chapter treat an end as happening before a start at the same time.

Java
1static void halfOpenInJava() {
2 String s = "abcdefgh";
3 String part = s.substring(2, 5); // half-open [2, 5): characters at 2, 3 and 4
4 System.out.println(part); // cde
5 System.out.println(part.length() == 5 - 2); // length is end - start
6 // adjacent half-open ranges tile exactly
7 System.out.println(s.substring(0, 2) + s.substring(2, 5) + s.substring(5, 8)); // abcdefgh
8}

Do two intervals overlap? Closed, half-open and open

Interactive · edit the input

Your input

Try

I1

[1, 4]

I2

[4, 7]
01234567
Setup

Two intervals overlap when they share at least one point (or, for the open conventions, a stretch of positive length). The convention for the endpoints decides what touching ends mean: closed intervals include both ends, so touching counts.

Step 1 / 4
Java
1static boolean overlaps(int a1, int b1, int a2, int b2, int kind) { // kind 0: closed, 1: half-open, 2: open
2 if (kind == 0) return a1 <= b2 && a2 <= b1; // closed: sharing a single point counts as overlap
3 return a1 < b2 && a2 < b1; // half-open and open: touching ends do not overlap
4}

Live state

convention

Closed [a, b]

No condition evaluated on this step
Execution trace (dry run table)Show 4 steps
StepLineStateWhat happens
1L1convention=Closed [a, b]Two intervals overlap when they share at least one point (or, for the open conventions, a stretch of positive length). The convention for the endpoints decides what touching ends mean: closed intervals include both ends, so touching counts.
2L2a1=1 b2=7The first interval starts before the second one ends.
3L2a2=4 b1=4The second interval starts before the first one ends.
4L2return=true intersection=[4, 4]They overlap; the shared part is [4, 4] (a single point).
22.01.3

Overlap definitions

Two intervals relate in one of a few ways. They are **disjoint** when one ends before the other starts, they **touch** when one's end equals the other's start, they **partially overlap**, one can be **nested** inside the other, or they are **equal**. One formula covers every case: two intervals overlap exactly when max(starts) < min(ends) for half-open intervals, or max(starts) <= min(ends) for closed ones, and in that case the intersection is [max(starts), min(ends)].

The negation is easier to remember: two intervals are disjoint exactly when one ends before the other starts. The animation's “Nested” preset shows the shared part [3, 5] being the whole inner interval, and “Clear overlap” shows [4, 6].

CaseExampleClosed overlap?Half-open overlap?
Disjoint[1, 2] and [3, 4]NoNo
Touching[1, 4] and [4, 7]Yes (the point 4)No
Partial overlap[1, 6] and [4, 9]Yes, [4, 6]Yes, [4, 6)
Nested[0, 10] and [3, 5]Yes, [3, 5]Yes, [3, 5)
Equal[2, 5] and [2, 5]YesYes
Java
1// half-open intersection; returns null when the intervals do not overlap
2static int[] intersect(int[] a, int[] b) {
3 int start = Math.max(a[0], b[0]);
4 int end = Math.min(a[1], b[1]);
5 return start < end ? new int[]{start, end} : null;
6}

Do two intervals overlap? Closed, half-open and open

Interactive · edit the input

Your input

Try

I1

[1, 4]

I2

[4, 7]
01234567
Setup

Two intervals overlap when they share at least one point (or, for the open conventions, a stretch of positive length). The convention for the endpoints decides what touching ends mean: closed intervals include both ends, so touching counts.

Step 1 / 4
Java
1static boolean overlaps(int a1, int b1, int a2, int b2, int kind) { // kind 0: closed, 1: half-open, 2: open
2 if (kind == 0) return a1 <= b2 && a2 <= b1; // closed: sharing a single point counts as overlap
3 return a1 < b2 && a2 < b1; // half-open and open: touching ends do not overlap
4}

Live state

convention

Closed [a, b]

No condition evaluated on this step
Execution trace (dry run table)Show 4 steps
StepLineStateWhat happens
1L1convention=Closed [a, b]Two intervals overlap when they share at least one point (or, for the open conventions, a stretch of positive length). The convention for the endpoints decides what touching ends mean: closed intervals include both ends, so touching counts.
2L2a1=1 b2=7The first interval starts before the second one ends.
3L2a2=4 b1=4The second interval starts before the first one ends.
4L2return=true intersection=[4, 4]They overlap; the shared part is [4, 4] (a single point).
22.01.4

Interval normalization

Real input is messy: endpoints may come reversed (start after end), empty intervals appear, and the same data may be given as inclusive ranges while the algorithm expects half-open. **Normalisation** converts every input into one clean convention before anything else runs: swap reversed endpoints (or reject them), convert inclusive [a, b] to half-open [a, b + 1), drop empty intervals, and optionally sort.

Do it at the boundary of your code, once, and write the rest of the program for half-open data only. Mixing conventions is where < and <= bugs come from.

Java
1// inclusive [a, b] pairs in any order -> sorted, non-empty half-open [a, b+1) pairs
2static int[][] normalize(int[][] inclusive) {
3 List<int[]> out = new ArrayList<>();
4 for (int[] r : inclusive) {
5 int lo = Math.min(r[0], r[1]);
6 int hi = Math.max(r[0], r[1]);
7 out.add(new int[]{lo, hi + 1}); // half-open: every integer range now has length >= 1
8 }
9 out.sort(Comparator.comparingInt(x -> x[0]));
10 return out.toArray(new int[0][]);
11}
22.01.5

Sorting endpoints

Almost every interval algorithm starts by sorting, and the sort key follows from the question. **Sort by start** when you sweep left to right and build things up (merging, inserting, intersections). **Sort by end** when you want to keep the interval that frees the line soonest (activity selection, minimum arrows). **Sort the endpoints themselves** as separate events when you only need counts (maximum overlap).

A tie-breaker is needed when keys are equal. For merging, ties on start do not change the result. For event sorting, the order of an end and a start at the same time decides whether touching intervals overlap, and the next lesson group builds on exactly that point. When intervals have equal starts and you want the longest one first, sort by start ascending and end descending.

Java
1// by start ascending; for equal starts, longer interval first
2static void sortByStartThenLongest(int[][] a) {
3 Arrays.sort(a, (x, y) -> x[0] != y[0] ? Integer.compare(x[0], y[0]) : Integer.compare(y[1], x[1]));
4}
5
6// by end ascending; for equal ends, later start first (the shorter interval)
7static void sortByEnd(int[][] a) {
8 Arrays.sort(a, (x, y) -> x[1] != y[1] ? Integer.compare(x[1], y[1]) : Integer.compare(y[0], x[0]));
9}

Merge overlapping intervals (sort by start, then scan)

Interactive · edit the input

Your input

Try

input

[8, 10]
[1, 3]
[15, 18]
[2, 6]
[9, 12]
0123456789101112131415161718
Setup

In input order, overlaps can be far apart. Sorting by start puts every overlap next to its neighbour.

Step 1 / 13
Java
1public static List<int[]> merge(int[][] intervals) {
2 Arrays.sort(intervals, Comparator.comparingInt(a -> a[0])); // sort by start
3 List<int[]> merged = new ArrayList<>();
4 for (int[] cur : intervals) {
5 int[] last = merged.isEmpty() ? null : merged.get(merged.size() - 1);
6 if (last != null && cur[0] <= last[1]) {
7 last[1] = Math.max(last[1], cur[1]); // overlap: extend
8 } else {
9 merged.add(cur); // gap: start a new interval
10 }
11 }
12 return merged;
13}

Live state

0 comparisons

intervals

5

No condition evaluated on this step
Execution trace (dry run table)Show 13 steps
StepLineStateWhat happens
1L1intervals=5In input order, overlaps can be far apart. Sorting by start puts every overlap next to its neighbour.
2L2intervals=5Sorted by start: O(n log n). Now one left-to-right scan is enough.
3L6start=1 end=3 last end=noneNothing merged yet: this interval starts the list.
4L9merged=1Append [1, 3] as a new merged interval.
5L6start=2 end=6 last end=3[2, 6] starts before the last merged interval ends, so they overlap.
6L7last end=6Extend the last interval: end = max(3, 6) = 6.
7L6start=8 end=10 last end=6[8, 10] starts after 6: there is a gap.
8L9merged=2Append [8, 10] as a new merged interval.
9L6start=9 end=12 last end=10[9, 12] starts before the last merged interval ends, so they overlap.
10L7last end=12Extend the last interval: end = max(10, 12) = 12.
11L6start=15 end=18 last end=12[15, 18] starts after 12: there is a gap.
12L9merged=3Append [15, 18] as a new merged interval.
13L12return=[1,6] [8,12] [15,18]5 intervals became 3. O(n log n) for the sort plus O(n) for the scan.

Practice ladder

Now try it yourself

Easy

  • Meeting Rooms
  • Summary Ranges

Medium

  • Merge Intervals
  • Interval List Intersections

Advanced

  • Data Stream as Disjoint Intervals
  • Range Module

Key takeaways

  • Decide whether endpoints are closed or half-open before writing any comparison.
  • Half-open [start, end) is the convention of most programming: length is end - start and neighbours tile exactly.
  • Intervals overlap when max(starts) < min(ends) (half-open), and the intersection is [max(starts), min(ends)).
  • Normalise to one convention at the input boundary.
  • Sort by start to build, by end to select, and use sorted events to count.