DSA DSA In Depth
0%
lesson fenwick-treebinary-indexed-treerange-queries

Fenwick Trees (Binary Indexed Trees): lowbit Geometry, Point Updates & Range Updates

7 min read Module 3.3: Range Query & Sublinear Decomposition Structures Updated Oct 23, 2026
📄 Cheat Sheet
Fenwick Tree (Binary Indexed Tree) — Point Update & Prefix Sum
Tushar Roy - Coding Made Simple Fenwick Tree (Binary Indexed Tree) — Point Update & Prefix Sum Watch on YouTube ↗

Demonstrates how two's complement bit isolation i & -i links every 1-based index to its parent responsibility interval.

0:00 / --:--

1. Two's Complement Geometry & The Responsibility Interval

In a 1-indexed Fenwick Tree $T[1 \dots N]$, let $\text{lowbit}(i) = i \mathrel{\&} (-i)$ denote the largest power of two $2^k$ that divides $i$. Because $-i = \sim i + 1$ in two's complement binary representation, all bits below $2^k$ flip from $0 \to 1$ and carry into position $k$, while all bits above $k$ are inverted and masked out by the bitwise AND.

Fenwick Responsibility Invariant: Each cell $T[i]$ (for $1 \le i \le N$) stores the aggregate of the half-open interval of length $\text{lowbit}(i)$ ending at $i$: $$T[i] = \sum_{j = i - \text{lowbit}(i) + 1}^{i} A[j]$$
  • If $i$ is odd ($\text{lowbit}(i) = 1$), $T[i]$ stores only $A[i]$.
  • If $i = 6 = 0110_2$ ($\text{lowbit}(6) = 2$), $T[6]$ stores $A[5] + A[6]$.
  • If $i = 8 = 1000_2$ ($\text{lowbit}(8) = 8$), $T[8]$ stores $A[1] + A[2] + \dots + A[8]$.
Fenwick Tree T[1..8]: Responsibility Intervals [i - lowbit(i) + 1 .. i] T[8] (1000₂, lowbit=8): covers A[1..8] T[4] (0100₂, lowbit=4): A[1..4] T[2] (0010₂): A[1..2] T[6] (0110₂): A[5..6] T[1] T[3] T[5] T[7] i=1 (0001) i=2 (0010) i=3 (0011) i=4 (0100) i=5 (0101) i=6 (0110) i=7 (0111) i=8 (1000) prefixSum(7) = T[7] + T[6] + T[4] | add(3, v) updates T[3] -> T[4] -> T[8]

2. $O(N)$ Linear-Time Construction & $O(\log N)$ Binary Lifting (lowerBound)

Calling add(i, A[i]) $N$ times builds a Fenwick tree in $O(N \log N)$ time. We can instead build it in exact $\Theta(N)$ time by propagating each node $T[i]$ directly to its immediate update parent $j = i + (i \mathrel{\&} -i)$ in a single forward loop:

$$\text{For } i = 1 \dots N: \quad j = i + (i \mathrel{\&} -i); \quad \text{if } j \le N \text{ then } T[j] \mathrel{+}= T[i]$$

Furthermore, if all array elements are non-negative ($A[i] \ge 0$), the prefix sums are monotonically non-decreasing. Instead of binary searching over prefixSum(mid) in $O(\log^2 N)$ time, we can find the smallest index $i$ with $\text{prefixSum}(i) \ge k$ in single-pass $O(\log N)$ time via Binary Lifting: test bit steps $p = 2^{\lfloor \log_2 N \rfloor}, \dots, 2, 1$; if $T[\text{idx} + p] < k$, advance $\text{idx} \mathrel{+}= p$ and subtract $k \mathrel{-}= T[\text{idx}]$ because $T[\text{idx} + p]$ stores the exact sum of the next $p$ elements!

flowchart LR subgraph UpdateTree["Update Tree (i += i & -i)"] U1["1 (001)"] --> U2["2 (010)"] U3["3 (011)"] --> U4["4 (100)"] U2 --> U4 U5["5 (101)"] --> U6["6 (110)"] U7["7 (111)"] --> U8["8 (1000)"] U6 --> U8 U4 --> U8 end subgraph QueryTree["Query Tree (i -= i & -i)"] Q7["7 (111)"] --> Q6["6 (110)"] --> Q4["4 (100)"] --> Q0["0 (stop)"] Q5["5 (101)"] --> Q4 Q3["3 (011)"] --> Q2["2 (010)"] --> Q0 end

3. Range Add & Range Sum Using Two Dual Fenwick Trees

Let $D[i] = A[i] - A[i-1]$ be the difference array of $A$, so adding $+\Delta$ to $A[l \dots r]$ only requires two point updates: $D[l] \mathrel{+}= \Delta$ and $D[r+1] \mathrel{-}= \Delta$. How do we recover the prefix sum $\sum_{i=1}^{x} A[i]$ from $D$? Using summation by parts:

$$\sum_{i=1}^{x} A[i] = \sum_{i=1}^{x} \sum_{j=1}^{i} D[j] = \sum_{j=1}^{x} D[j] \cdot (x - j + 1) = (x + 1)\sum_{j=1}^{x} D[j] - \sum_{j=1}^{x} j \cdot D[j]$$

By maintaining two Fenwick trees—$B_1$ storing $D[j]$ and $B_2$ storing $j \cdot D[j]$—both rangeAdd(l, r, delta) and rangeSum(l, r) execute in $O(\log N)$ time with only $2N$ words of memory!

Hardware & Cache Reality — Why Fenwick Beats Segment Trees by 3x–5x: A Fenwick tree uses exactly $N + 1$ contiguous words (4 MB for $10^6$ int32s, fitting entirely inside a modern CPU's L3 cache), has zero recursion overhead, and its loop condition i -= i & -i executes in $\frac{1}{2} \log_2 N$ iterations on average (the average Hamming weight of an integer). On modern x86-64 CPUs with BMI1 (BLSR for i & (i - 1) and BLSI for i & -i), the inner loop is literally 3 machine instructions.
Fenwick Tree (BIT) lowbit(i) = i & −i Bit-Jump & Binary Lifting
O(log N) · N Words Memory

Trace how Fenwick Tree prefix queries strip the lowest set bit (i −= i & −i) and point updates add the lowest set bit (i += i & −i), visiting at most popcount(i) ≤ log₂ N nodes.

Step 1 / 9
Custom Input:
Timeline

Invariant & State

Index i = 7 (0111₂) | lowbit(i) = i & −i = 1 | Covers interval of length 1: [7..7] (0111₂)
🌳 Live 2D Hierarchical Tree Topology — Fenwick Tree (BIT) lowbit(i) = i & −i Bit-Jump & Binary Lifting Visited/Active Pivot/Root Resolved
[1..1] ( T[1] [0] [1..2] ( T[2] [1] [3..3] ( T[3] [2] [1..4] ( T[4] [3] [5..5] ( T[5] [4] [5..6] ( T[6] [5] ACTIVE ( T[7] [6] [1..8] ( T[8] [7]
[0]T[1][1..1] (0001₂)
[1]T[2][1..2] (0010₂)
[2]T[3][3..3] (0011₂)
[3]T[4][1..4] (0100₂)
[4]T[5][5..5] (0101₂)
[5]T[6][5..6] (0110₂)
[6]T[7]ACTIVE (0111₂)
[7]T[8][1..8] (1000₂)
Current Index i
7 (0111₂)
lowbit (i & -i)
1
Next Index
6
Bit Jumps
1 ≤ log₂(8)
[Step 1] prefixSum(7): i=7 (0111₂), lowbit=1. Accumulate T[7] (range [7..7]), strip lowest bit -> i = 7 - 1 = 6.

4. Production Implementation: Fenwick Tree with $O(N)$ Build, Binary Lifting & Range Add/Sum

class FenwickTree {
  /**
   * O(N) linear-time construction from 0-indexed input array
   */
  constructor(arrOrSize) {
    if (typeof arrOrSize === "number") {
      this.n = arrOrSize;
      this.tree = new Float64Array(this.n + 1);
    } else {
      this.n = arrOrSize.length;
      this.tree = new Float64Array(this.n + 1);
      for (let i = 1; i <= this.n; i++) {
        this.tree[i] += arrOrSize[i - 1];
        const parent = i + (i & -i);
        if (parent <= this.n) this.tree[parent] += this.tree[i];
      }
    }
  }

  // Point update: A[idx] += delta (1-indexed: 1 <= idx <= n)
  add(idx, delta) {
    for (let i = idx; i <= this.n; i += i & -i) {
      this.tree[i] += delta;
    }
  }

  // Prefix sum: A[1] + ... + A[idx]
  prefixSum(idx) {
    let sum = 0;
    for (let i = idx; i > 0; i -= i & -i) {
      sum += this.tree[i];
    }
    return sum;
  }

  rangeSum(l, r) {
    return this.prefixSum(r) - this.prefixSum(l - 1);
  }

  /**
   * O(log N) Binary Lifting on Fenwick Tree (requires A[i] >= 0):
   * Returns smallest 1-based index idx such that prefixSum(idx) >= targetSum
   */
  lowerBound(targetSum) {
    let idx = 0;
    let bitMask = 1 << (31 - Math.clz32(this.n));
    for (; bitMask > 0; bitMask >>= 1) {
      const next = idx + bitMask;
      if (next <= this.n && this.tree[next] < targetSum) {
        idx = next;
        targetSum -= this.tree[next];
      }
    }
    return idx + 1;
  }
}

/**
 * Dual Fenwick Tree supporting both Range Add and Range Sum in O(log N)
 */
class RangeFenwickTree {
  constructor(n) {
    this.n = n;
    this.b1 = new FenwickTree(n); // stores D[i]
    this.b2 = new FenwickTree(n); // stores i * D[i]
  }

  rangeAdd(l, r, delta) {
    this.b1.add(l, delta);
    this.b1.add(r + 1, -delta);
    this.b2.add(l, delta * l);
    this.b2.add(r + 1, -delta * (r + 1));
  }

  prefixSum(x) {
    return (x + 1) * this.b1.prefixSum(x) - this.b2.prefixSum(x);
  }

  rangeSum(l, r) {
    return this.prefixSum(r) - this.prefixSum(l - 1);
  }
}

5. Complexity Matrix

Operation Standard Fenwick Tree Dual Fenwick Tree ($B_1, B_2$) Iterative Segment Tree
Construction from Array $\Theta(N)$ $\Theta(N)$ $\Theta(N)$
Point Update / Prefix Query $O(\log N)$ $O(\log N)$ $O(\log N)$
Range Add + Range Sum Not directly (needs Dual BIT) $O(\log N)$ $O(\log N)$ (Lazy Segment Tree)
Prefix lowerBound Search $O(\log N)$ via Binary Lifting — $O(\log N)$ tree walk
Memory Footprint $N + 1$ words $2N + 2$ words $2N$ to $4N$ words
Common Pitfall — 0-Indexed Infinite Loops & Non-Invertible Arbitrary Range Queries: Never pass index 0 to add(0, delta) in a 1-indexed Fenwick Tree! Because 0 & -0 === 0, the loop for (let i = 0; i <= n; i += i & -i) never advances and hangs forever. Always use 1-based indexing inside the tree (or the 0-based variant i |= i + 1 / i = (i & (i + 1)) - 1). Also remember that while a Fenwick tree supports prefix $\min(1..r)$ when updates only decrease values, it cannot compute arbitrary $\min(l..r)$ via subtraction because $\min$ has no inverse.
Check yourself Question 1 of 4
Concept Check Responsibility Interval of a Fenwick Tree Node recall

In a 1-indexed Fenwick Tree (Binary Indexed Tree) $T[1..N]$, let $\text{lowbit}(i) = i \mathrel{\&} (-i)$ in two's complement arithmetic.

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Why does Binary Lifting on the Fenwick array $T[]$ find this index in $O(\log N)$ time with a single array read per bit, whereas binary searching over `prefixSum(mid)` takes $O(\log^2 N)$?

Key Takeaways

A Fenwick tree is a segment tree with all right children removed, storing the aggregate of [i - lowbit(i) + 1 .. i] at 1-based index T[i] where lowbit(i) = i & -i.
Passing each node's value to its immediate parent i + (i & -i) constructs a Fenwick tree in exact O(N) time.
Binary lifting on T[] finds the first prefix sum >= K in O(log N) time because T[idx + 2^p] holds the exact sum of the next 2^p elements.
Maintaining two Fenwick trees over D[i] and i * D[i] supports both range additions and range sum queries in O(log N) time.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue