Search for all intervals that overlap a query interval using a balanced interval tree.
Interval tree search finds all intervals that overlap a given query interval. It extends a binary search tree by storing interval metadata at each node to support efficient range queries.
Each node represents an interval and maintains auxiliary information to guide pruning during search.
Problem
Given a set of intervals and a query interval , find all intervals such that:
This is the standard overlap condition.
Structure
Each node stores:
| field | meaning |
|---|---|
| interval | stored interval |
| max_r | maximum right endpoint in subtree |
| left | left child |
| right | right child |
The key property is:
Algorithm
Search starts at the root and prunes subtrees that cannot contain overlapping intervals.
interval_tree_search(node, ql, qr, result):
if node == null:
return
if node.left != null and node.left.max_r >= ql:
interval_tree_search(node.left, ql, qr, result)
if node.interval overlaps [ql, qr]:
add node.interval to result
if node.right != null and node.interval.l <= qr:
interval_tree_search(node.right, ql, qr, result)Example
Intervals:
[15, 20], [10, 30], [17, 19], [5, 20], [12, 15], [30, 40]Query:
Overlapping intervals:
| interval | overlap |
|---|---|
| [15, 20] | yes |
| [10, 30] | yes |
| [12, 15] | yes |
Correctness
An interval overlaps the query if and only if it satisfies:
The tree stores in each subtree, which represents the furthest right endpoint reachable below that node.
If , no interval in that subtree can overlap the query, so pruning is safe.
Traversal ensures that every subtree that could contain overlapping intervals is visited, and every visited interval is checked explicitly. Therefore all overlapping intervals are found and no non-overlapping interval is incorrectly included.
Complexity
Let be the number of intervals and be the number of reported results.
| case | time |
|---|---|
| search | |
| worst case (all overlap) |
Space complexity:
| version | space |
|---|---|
| recursive |
where is tree height.
When to Use
Interval trees are useful when:
- you need fast overlap queries
- intervals are dynamic (insert/delete supported)
- scheduling, compilers, or computational geometry tasks are involved
- range conflict detection is required
Compared to segment trees, interval trees handle arbitrary intervals more naturally without discretization.
Implementation
class Node:
def __init__(self, interval):
self.interval = interval
self.max_r = interval[1]
self.left = None
self.right = None
def overlaps(a, b):
return a[0] <= b[1] and a[1] >= b[0]
def interval_tree_search(node, ql, qr, result):
if node is None:
return
if node.left and node.left.max_r >= ql:
interval_tree_search(node.left, ql, qr, result)
if overlaps(node.interval, (ql, qr)):
result.append(node.interval)
if node.right and node.interval[0] <= qr:
interval_tree_search(node.right, ql, qr, result)type Interval struct {
L int
R int
}
type Node struct {
Int Interval
MaxR int
Left *Node
Right *Node
}
func overlaps(a Interval, ql, qr int) bool {
return a.L <= qr && a.R >= ql
}
func IntervalTreeSearch(node *Node, ql, qr int, res *[]Interval) {
if node == nil {
return
}
if node.Left != nil && node.Left.MaxR >= ql {
IntervalTreeSearch(node.Left, ql, qr, res)
}
if overlaps(node.Int, ql, qr) {
*res = append(*res, node.Int)
}
if node.Right != nil && node.Int.L <= qr {
IntervalTreeSearch(node.Right, ql, qr, res)
}
}