Overview
A segment tree is a binary tree over array ranges. The root covers [0, n - 1], each node covers a range and stores an aggregate of it (sum, min, max, gcd), and each leaf covers one element. Any range query is answered by combining O(log n) nodes, and updating one element changes only the O(log n) nodes on its path to the root.
It is the go-to structure when an array receives both updates and range queries mixed together, where prefix sums (O(n) per update) fail. With lazy propagation, it also supports range updates like 'add 5 to every element in [l, r]' in O(log n).
Each manager knows the total sales of their team. To get the sales of several teams, the CEO asks a few managers rather than every employee. When one employee makes a sale, only their manager chain up to the CEO needs updating.
When to use it
- Range sum / min / max / gcd queries with point updates interleaved.
- Range updates and range queries together (lazy propagation).
- Counting problems after coordinate compression (count smaller elements).
- Any associative combine operation over ranges.
Problem patterns it solves
Recognize it when: update(i, val) and sumRange(l, r) interleaved.
- 307. Range Sum Query - Mutable
- 2407. Longest Increasing Subsequence II
Recognize it when: min or max over many ranges with updates.
- 2286. Booking Concert Tickets in Groups
- 1353. Maximum Number of Events That Can Be Attended (variant)
Recognize it when: count of smaller / larger elements before or after i.
- 315. Count of Smaller Numbers After Self
- 327. Count of Range Sum
- 493. Reverse Pairs
Recognize it when: add or assign over ranges, then query.
- 732. My Calendar III
- 699. Falling Squares
- 715. Range Module
Where it is used in real software
Dashboards answering min / max / sum over arbitrary time windows while new data points arrive use segment-tree-like aggregations.
Rectangle union area and interval stabbing queries are solved with segment trees over sweep lines.
Range queries over player rankings or map regions with frequent updates.
Some engines maintain aggregate trees to answer range aggregates without scanning every row.
Key terms
- Node range
- [start, end] of the array covered by a node.
- Merge function
- Associative operation combining children: sum, min, max, gcd.
- Identity
- Neutral value for the merge: 0 for sum, Infinity for min.
- Lazy propagation
- Store pending range updates at a node and push them to children only when needed.
- 4n array
- An array of size 4n is always large enough for a recursive segment tree.
Query [l, r] recursively
- 1No overlap
If the node's range is outside [l, r], return the identity (0 for sum).
- 2Full overlap
If the node's range is inside [l, r], return the node's stored value.
- 3Partial overlap
Query both children and merge their results.
- 4Point update
Recurse into the child containing the index, update the leaf, then recompute each ancestor on the way back.
Sum query on [1, 4] for arr = [2, 1, 5, 3, 4, 2]
Root [0, 5] = 17; children [0, 2] = 8 and [3, 5] = 9
| Node range | Stored sum | Overlap with [1, 4] | Contributes |
|---|---|---|---|
| [0, 5] | 17 | partial | ask children |
| [0, 2] | 8 | partial | ask children |
| [0, 1] | 3 | partial | ask children |
| [0, 0] | 2 | none | 0 |
| [1, 1] | 1 | full | 1 |
| [2, 2] | 5 | full | 5 |
| [3, 5] | 9 | partial | ask children |
| [3, 4] | 7 | full | 7 |
| [5, 5] | 2 | none | 0 |
NOWNode range: [0, 5] | Stored sum: 17 | Overlap with [1, 4]: partial | Contributes: ask children
sum(1..4) = 1 + 5 + 7 = 13. Only O(log n) nodes contribute; the node [3, 4] answered two elements at once without visiting its leaves.
Implementation
class SegmentTree { constructor(arr) { this.n = arr.length; this.tree = new Array(4 * this.n).fill(0); this.#build(arr, 1, 0, this.n - 1); } #build(arr, node, start, end) { if (start === end) { this.tree[node] = arr[start]; return; } const mid = (start + end) >> 1; this.#build(arr, 2 * node, start, mid); this.#build(arr, 2 * node + 1, mid + 1, end); this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1]; } update(index, value, node = 1, start = 0, end = this.n - 1) { if (start === end) { this.tree[node] = value; return; } const mid = (start + end) >> 1; if (index <= mid) this.update(index, value, 2 * node, start, mid); else this.update(index, value, 2 * node + 1, mid + 1, end); this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1]; } query(l, r, node = 1, start = 0, end = this.n - 1) { if (r < start || end < l) return 0; // no overlap if (l <= start && end <= r) return this.tree[node]; // full overlap const mid = (start + end) >> 1; return this.query(l, r, 2 * node, start, mid) + this.query(l, r, 2 * node + 1, mid + 1, end); }} const st = new SegmentTree([2, 1, 5, 3, 4, 2]);st.query(1, 4); // 13st.update(2, 10);st.query(1, 4); // 18Complexity and performance
Each node computed once.
One root-to-leaf path.
At most about 4 nodes per level.
Defers work to later queries.
Array-based recursive layout.
Trade-offs
Prefix sums are O(1) query but O(n) update. Use them for static arrays; use a segment tree when updates are frequent.
Fenwick trees are shorter and faster for sums and prefix-based queries. Segment trees handle min / max, non-invertible merges, and lazy range updates more naturally.
Segment trees are long to write under interview pressure; practice the template until it is automatic.
Variants and related techniques
Size 2n array with leaves at n..2n - 1; shorter and faster for point updates.
Create nodes on demand for huge coordinate ranges (10^9).
Each node stores its sorted range; answers 'count values <= x in [l, r]'.
Keeps old versions after updates; answers kth smallest in a range.
Common mistakes
- Allocating 2n instead of 4n for the recursive version.
Fix: Use 4n to avoid index overflow for non-power-of-two sizes.
- Wrong identity for min queries.
Fix: Return Infinity for no overlap, not 0.
- Forgetting to push lazy values before recursing.
Fix: Push pending updates to children before visiting them in partial overlaps.
Interview questions
Why is a range query O(log n)?
At each level of the tree, at most two nodes partially overlap the query range; fully covered nodes return immediately. With log n levels, only O(log n) nodes are visited.
What is lazy propagation?
Instead of updating every element in a range immediately, store the pending update at the highest fully covered nodes and push it to children only when a later operation needs to go deeper.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 307. Range Sum Query - Mutable | Medium | Core template. |
| 2286. Booking Concert Tickets in Groups | Hard | Max and sum together. |
| 315. Count of Smaller Numbers After Self | Hard | Coordinate compression. |
| 732. My Calendar III | Hard | Range add, max query. |
| 699. Falling Squares | Hard | Range assign with lazy. |