Segment Tree and Range Query Patterns: Fenwick Tree, Lazy Propagation, Order Statistics

Segment Tree and Range Query Interview Patterns

Segment trees solve range query problems — finding the sum, min, max, or GCD over an array interval — with O(log n) query and update time. They appear in hard LeetCode problems and senior-level interviews at companies that care about algorithmic depth. This guide covers the implementation and the pattern recognition needed to apply them correctly.

When to Use a Segment Tree

Use a segment tree when you need both:

  • Range queries: sum/min/max/GCD over array[i..j]
  • Point updates: update a single element

Complexity: O(n) build, O(log n) query and update. Prefix sum arrays are O(1) query but O(n) update. Segment trees are the right tool when you need both operations to be fast.

Implementation: Sum Segment Tree

class SegmentTree:
    def __init__(self, nums):
        self.n = len(nums)
        self.tree = [0] * (4 * self.n)
        self._build(nums, 0, 0, self.n - 1)

    def _build(self, nums, node, start, end):
        if start == end:
            self.tree[node] = nums[start]
        else:
            mid = (start + end) // 2
            self._build(nums, 2*node+1, start, mid)
            self._build(nums, 2*node+2, mid+1, end)
            self.tree[node] = self.tree[2*node+1] + self.tree[2*node+2]

    def update(self, idx, val, node=0, start=0, end=None):
        if end is None: end = self.n - 1
        if start == end:
            self.tree[node] = val
            return
        mid = (start + end) // 2
        if idx <= mid: self.update(idx, val, 2*node+1, start, mid)
        else:          self.update(idx, val, 2*node+2, mid+1, end)
        self.tree[node] = self.tree[2*node+1] + self.tree[2*node+2]

    def query(self, l, r, node=0, start=0, end=None):
        if end is None: end = self.n - 1
        if r < start or end < l: return 0          # out of range
        if l <= start and end <= r: return self.tree[node]  # fully covered
        mid = (start + end) // 2
        return (self.query(l, r, 2*node+1, start, mid) +
                self.query(l, r, 2*node+2, mid+1, end))

Range Sum Query – Mutable (LeetCode 307)

class NumArray:
    def __init__(self, nums):
        self.st = SegmentTree(nums)

    def update(self, index, val):
        self.st.update(index, val)

    def sumRange(self, left, right):
        return self.st.query(left, right)

Binary Indexed Tree (Fenwick Tree) — Simpler for Range Sum

When you only need range sum + point update (not min/max/other operations), the Fenwick tree is simpler to implement:

class BIT:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (n + 1)

    def update(self, i, delta):  # i is 1-indexed
        while i  0:
            s += self.tree[i]
            i -= i & (-i)        # remove lowest set bit
        return s

    def range_sum(self, l, r):
        return self.prefix_sum(r) - self.prefix_sum(l - 1)

Fenwick tree uses O(n) space (vs. O(4n) for segment tree), has simpler code, but only supports commutative/invertible operations (sum, XOR). Use segment tree for min/max.

Count of Smaller Numbers After Self (LeetCode 315)

For each element, count how many elements to its right are smaller. Use a Fenwick tree over value coordinates:

def count_smaller(nums):
    # Coordinate compress: map values to [1..n]
    sorted_unique = sorted(set(nums))
    rank = {v: i+1 for i, v in enumerate(sorted_unique)}
    n = len(nums)
    bit = BIT(len(sorted_unique))
    result = []
    for num in reversed(nums):
        r = rank[num]
        result.append(bit.prefix_sum(r - 1))  # count elements with rank < r
        bit.update(r, 1)                        # register this element
    return result[::-1]

Lazy Propagation (Range Update)

When you need range updates (add 5 to all elements in [l..r]) and range queries, use lazy propagation to defer updates:

# Standard segment tree pushes updates down to children only when queried.
# lazy[node] stores a pending update not yet applied to children.
# On query/update reaching a node with lazy != 0:
#   1. Apply lazy to node value
#   2. Push lazy to children: lazy[2*node+1] += lazy[node]; lazy[2*node+2] += lazy[node]
#   3. Clear: lazy[node] = 0
# This keeps range update at O(log n) instead of O(n).

Common Problems by Pattern

Problem Type Tool LeetCode
Range sum, point update Fenwick tree 307, 315, 327
Range min/max, point update Segment tree 699, 715, 2407
Range update, range query Segment tree + lazy 732, 1505
Order statistics (kth smallest) Segment tree on values 315, 493, 2426

Interview Tips

  • Implement Fenwick tree for range sum — it is 15 lines vs. 40+ for segment tree and impresses interviewers with conciseness
  • The lowbit trick i & (-i) is the key to the Fenwick tree — explain it: -i in two's complement flips all bits and adds 1, leaving only the lowest set bit
  • When asked about range min/max, pivot to segment tree and explain why Fenwick doesn't work (min is not invertible)
  • For the coordinate compression pattern (used in 315, 327, 493) — sorting values and mapping to ranks is a powerful technique beyond segment trees

  • Atlassian Interview Guide
  • Coinbase Interview Guide
  • LinkedIn Interview Guide
  • Apple Interview Guide
  • Databricks Interview Guide
  • Meta Interview Guide
  • {
    “@context”: “https://schema.org”,
    “@type”: “FAQPage”,
    “mainEntity”: [
    {
    “@type”: “Question”,
    “name”: “When should you use a segment tree vs. a Fenwick tree vs. a prefix sum array?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “Prefix sum array: O(n) build, O(1) range query, O(n) point update. Use when the array is immutable. Fenwick tree (Binary Indexed Tree): O(n) build, O(log n) range sum query, O(log n) point update. Use for range sum with updates – simpler code than segment tree. Only works for invertible operations (sum, XOR). Segment tree: O(n) build, O(log n) any range query (sum, min, max, GCD), O(log n) point update, O(log n) range update with lazy propagation. Use when: (1) you need range min/max (Fenwick tree cannot do this), (2) you need range updates, (3) you need custom aggregation functions. The rule: reach for Fenwick first for range sum; use segment tree when Fenwick is insufficient.” }
    },
    {
    “@type”: “Question”,
    “name”: “How does the Fenwick tree (BIT) lowbit trick work?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “The lowbit of i is i & (-i), which extracts the least significant set bit. In two's complement, -i = ~i + 1, which flips all bits then adds 1, leaving only the lowest set bit unchanged. In the Fenwick tree, each index i stores the sum of elements in a range whose length is determined by lowbit(i): tree[i] covers [i – lowbit(i) + 1, i]. For update(i): add delta to tree[i] and propagate up by repeatedly doing i += lowbit(i). For prefix_sum(i): sum tree[i] and go down by doing i -= lowbit(i). Each update touches O(log n) nodes (one per bit in i). This gives O(log n) update and query with just array indexing – no tree structure needed.” }
    },
    {
    “@type”: “Question”,
    “name”: “How do you solve Count of Smaller Numbers After Self using a Fenwick tree?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “The key insight: process elements right to left. For each element, query how many smaller elements have already been seen (i.e., are to the right). Use a Fenwick tree indexed by element value (coordinate-compressed to [1..n]). Algorithm: (1) sort unique values and assign ranks 1..k, (2) traverse nums right to left, (3) for each num with rank r: query prefix_sum(r-1) for count of smaller elements seen so far, then update(r, 1) to register this element, (4) prepend the count to result. Coordinate compression maps arbitrary values to [1..n] for use as Fenwick tree indices. Time: O(n log n). This pattern (offline + Fenwick + coordinate compression) solves LeetCode 315, 327, 493.” }
    }
    ]
    }

    Scroll to Top