A mutable, self-balancing interval tree for JavaScript/TypeScript.
Written in TypeScript with no external dependencies. Uses a red-black tree under the hood for O(log n) operations.
npm install intervaltree
import { Interval, IntervalTree } from 'intervaltree'
const tree = new IntervalTree()
tree.addInterval(1, 5, 'data for 1-5')
tree.addInterval(3, 7)
tree.addInterval(8, 10)
// Query by point
const intervals = tree.searchPoint(4) // Returns intervals containing point 4
// Result: [Interval(1, 5, 'data for 1-5'), Interval(3, 7)]
// Query by range (overlaps)
const overlapping = tree.searchOverlap(6, 9) // Returns intervals overlapping [6, 9)
// Result: [Interval(3, 7), Interval(8, 10)]
import { Interval, IntervalTree } from 'intervaltree'
// Empty tree
const tree = new IntervalTree()
// From array of Interval objects
const intervals = [
new Interval(1, 3, 'first'),
new Interval(5, 8, 'second'),
new Interval(7, 10, 'third')
]
const tree2 = new IntervalTree(intervals)
// From tuples
const tree3 = IntervalTree.fromTuples([
[1, 3],
[5, 8],
[7, 10, 'with data']
])
// Add with explicit Interval object
tree.add(new Interval(1, 5, 'my data'))
// Add with convenience method
tree.addInterval(10, 15, 'more data')
// Add multiple intervals at once
tree.addAll([
new Interval(20, 25),
new Interval(30, 35, 'bulk add')
])
// Note: Duplicate intervals (same start, end, and data) are ignored
tree.addInterval(1, 5)
tree.addInterval(1, 5) // This is a no-op
console.log(tree.size) // 1
// Find all intervals containing a point
const pointResult = tree.searchPoint(6)
// Returns: Array of Interval objects containing point 6
// Find all intervals overlapping a range
const overlapResult = tree.searchOverlap(5, 10)
// Returns: All intervals that overlap with [5, 10)
// Find intervals completely enveloped by a range
const enveloped = tree.searchEnvelop(0, 100)
// Returns: All intervals where start >= 0 and end <= 100
// Find intervals by minimum length starting at/after a point
const byLength = tree.searchByLengthStartingAt(3, 5)
// Returns: All intervals of length >= 3 starting at position 5 or later
// Find first interval of minimum length
const first = tree.findOneByLengthStartingAt(3, 5)
// Returns: First interval of length >= 3 starting at/after position 5
// If found interval starts before 5, it's adjusted to start at 5
const interval = new Interval(4, 7, { id: 123, name: 'test' })
console.log(interval.start) // 4
console.log(interval.end) // 7
console.log(interval.data) // { id: 123, name: 'test' }
console.log(interval.length) // 3
// Check if interval contains a point
interval.containsPoint(5) // true
interval.containsPoint(7) // false (end is exclusive)
// Check if interval overlaps with a range
interval.overlapsWith(3, 6) // true
// Remove a specific interval
const toRemove = new Interval(1, 5, 'my data')
tree.remove(toRemove)
// Remove multiple intervals
tree.removeAll([interval1, interval2])
// Remove all intervals overlapping a range
tree.removeEnveloped(5, 10)
// Removes only intervals completely contained within [5, 10)
The chop
method removes a section from all intervals in the tree, splitting intervals that partially overlap the chopped region.
const tree = new IntervalTree()
tree.addInterval(0, 10)
tree.addInterval(5, 15)
tree.addInterval(12, 20)
// Remove the region [7, 13) from all intervals
tree.chop(7, 13)
console.log(tree.toTuples())
// Result: [[0, 7], [13, 15], [13, 20]]
// The interval [0, 10] becomes [0, 7]
// The interval [5, 15] becomes [13, 15]
// The interval [12, 20] becomes [13, 20]
const tree = IntervalTree.fromTuples([
[1, 5],
[4, 8], // Overlaps with [1, 5]
[10, 12],
[11, 15] // Overlaps with [10, 12]
])
tree.mergeOverlaps()
console.log(tree.toTuples())
// Result: [[1, 8], [10, 15]]
// Get all intervals as array
const allIntervals = tree.toArray()
// Get sorted array of intervals
const sorted = tree.toSorted() // Sorted by start, then end
// Get as array of tuples
const tuples = tree.toTuples() // [[start, end], [start, end, data], ...]
// Get tree size
console.log(tree.size) // Number of intervals in tree
// Clone the tree
const cloned = tree.clone()
// Get string representation
console.log(tree.toString())
// Output: "IntervalTree([ Interval(1, 5, length=4), Interval(10, 15, length=5) ])"
// Verify tree structure (for debugging, only in development mode)
tree.verify()
Since intervals work with numbers, you can use timestamps for date-based intervals:
const schedule = new IntervalTree()
// Add time slots (using timestamps)
const start = new Date('2024-01-01T09:00:00').getTime()
const end = new Date('2024-01-01T10:00:00').getTime()
schedule.addInterval(start, end, 'Morning meeting')
// Query for a specific time
const when = new Date('2024-01-01T09:30:00').getTime()
const conflicts = schedule.searchPoint(when)
// Find available slots
const dayStart = new Date('2024-01-01T08:00:00').getTime()
const minDuration = 60 * 60 * 1000 // 1 hour in milliseconds
const available = schedule.findOneByLengthStartingAt(minDuration, dayStart)
constructor(intervals?: Interval[])
- Create a new tree, optionally with initial intervalsstatic fromTuples(tuples: Array<[number, number] | [number, number, unknown]>)
- Create from tuple arrayadd(interval: Interval)
- Add an interval to the treeaddInterval(start: number, end: number, data?: unknown)
- Convenience method to add intervaladdAll(intervals: Interval[])
- Add multiple intervalsremove(interval: Interval)
- Remove a specific intervalremoveAll(intervals: Interval[])
- Remove multiple intervalsremoveEnveloped(start: number, end: number)
- Remove intervals contained within rangesearchPoint(point: number)
- Find all intervals containing a pointsearchOverlap(start: number, end: number)
- Find all intervals overlapping a rangesearchEnvelop(start: number, end: number)
- Find intervals completely within a rangesearchByLengthStartingAt(length: number, start: number)
- Find intervals by minimum lengthfindOneByLengthStartingAt(minLength: number, startingAt: number, filterFn?: (iv: Interval) => boolean)
- Find first matching intervalchop(start: number, end: number)
- Remove a region from all intervalsmergeOverlaps()
- Merge all overlapping intervalsclone()
- Create a deep copy of the treetoArray()
- Get all intervals as an arraytoSorted()
- Get all intervals sorted by start then endtoTuples()
- Get intervals as tuple arraysize
- Get the number of intervals in the tree
constructor(start: number, end: number, data?: unknown)
- Create an intervalstart
- Get the start point (inclusive)end
- Get the end point (exclusive)data
- Get the associated datalength
- Get the interval length (end - start)containsPoint(point: number)
- Check if interval contains a pointoverlapsWith(start: number, end: number)
- Check if interval overlaps with rangeequals(other: Interval)
- Check equality with another intervalstatic compare(a: Interval, b: Interval)
- Comparator function for sorting