Fenwick Trees

Reading time
2 min read
Word count
330 words
Diagram count
0 diagrams

Source: Victor Bona's Obsidian Compendium snapshot, Knowledge base/Data Structures/Fenwick Trees.md.

A Fenwick Tree (also called Binary Indexed Tree or BIT) is a data structure that efficiently supports prefix sum queries and point updates on an array. It achieves O(log n) for both operations using a clever bit manipulation technique, with less memory overhead than segment trees.

  • Intent: Provide efficient prefix sum queries and point updates with minimal space overhead.
  • Use Cases: Running frequency counts, cumulative statistics, range sum queries, inversion counting, 2D cumulative queries, competitive programming.
  • Key Properties:
    • O(log n) for query and update
    • Uses array of size n+1 (1-indexed)
    • Each index stores sum of specific range based on lowest set bit
    • Simpler than segment trees for sum queries

The Key Insight

Each index i is responsible for elements in range [i - lowbit(i) + 1, i]:

lowbit(i) = i & (-i)  // Lowest set bit

Index:  1    2    3    4    5    6    7    8
Binary: 001  010  011  100  101  110  111  1000
Lowbit: 1    2    1    4    1    2    1    8

Index 1 covers [1,1]   (1 element)
Index 2 covers [1,2]   (2 elements)
Index 3 covers [3,3]   (1 element)
Index 4 covers [1,4]   (4 elements)
Index 5 covers [5,5]   (1 element)
Index 6 covers [5,6]   (2 elements)
Index 7 covers [7,7]   (1 element)
Index 8 covers [1,8]   (8 elements)

Visual Representation

Array:    [_, 1, 3, 2, 5, 4, 6, 3, 7]  (1-indexed, index 0 unused)
BIT:      [_, 1, 4, 2, 10, 4, 10, 3, 30]

BIT[1] = A[1] = 1
BIT[2] = A[1] + A[2] = 4
BIT[3] = A[3] = 2
BIT[4] = A[1] + A[2] + A[3] + A[4] = 10
BIT[5] = A[5] = 4
BIT[6] = A[5] + A[6] = 10
BIT[7] = A[7] = 3
BIT[8] = A[1] + ... + A[8] = 30

Query prefix sum [1,6]:
  BIT[6] + BIT[4] = 10 + 10 = 20
  Path: 6 (110)  4 (100)  0

Implementation

class FenwickTree {
  private tree: number[];
  private n: number;

  constructor(n: number);
  constructor(arr: number[]);
  constructor(arg: number | number[]) {
    if (typeof arg === 'number') {
      this.n = arg;
      this.tree = new Array(this.n + 1).fill(0);
    } else {
      this.n = arg.length;
      this.tree = new Array(this.n + 1).fill(0);
      // Build from array
      for (let i = 0; i < arg.length; i++) {
        this.update(i + 1, arg[i]);
      }
    }
  }

  // Get lowest set bit
  private lowbit(i: number): number {
    return i & (-i);
  }

  // O(log n) - Add delta to index i (1-indexed)
  update(i: number, delta: number): void {
    while (i <= this.n) {
      this.tree[i] += delta;
      i += this.lowbit(i);
    }
  }

  // O(log n) - Get prefix sum [1, i]
  prefixSum(i: number): number {
    let sum = 0;
    while (i > 0) {
      sum += this.tree[i];
      i -= this.lowbit(i);
    }
    return sum;
  }

  // O(log n) - Get range sum [left, right] (1-indexed)
  rangeSum(left: number, right: number): number {
    return this.prefixSum(right) - this.prefixSum(left - 1);
  }

  // O(log n) - Set value at index (not add)
  set(i: number, value: number): void {
    const current = this.rangeSum(i, i);
    this.update(i, value - current);
  }
}

// Usage (1-indexed)
const bit = new FenwickTree([1, 3, 2, 5, 4, 6, 3, 7]);
console.log(bit.prefixSum(6));    // 21 (1+3+2+5+4+6)
console.log(bit.rangeSum(3, 6));  // 17 (2+5+4+6)
bit.update(4, 3);                 // Add 3 to index 4
console.log(bit.rangeSum(3, 6));  // 20 (2+8+4+6)

0-Indexed Wrapper

class FenwickTree0Indexed {
  private bit: FenwickTree;

  constructor(n: number);
  constructor(arr: number[]);
  constructor(arg: number | number[]) {
    if (typeof arg === 'number') {
      this.bit = new FenwickTree(arg);
    } else {
      this.bit = new FenwickTree(arg);
    }
  }

  // 0-indexed update
  update(i: number, delta: number): void {
    this.bit.update(i + 1, delta);
  }

  // 0-indexed prefix sum [0, i]
  prefixSum(i: number): number {
    return this.bit.prefixSum(i + 1);
  }

  // 0-indexed range sum [left, right]
  rangeSum(left: number, right: number): number {
    return this.bit.rangeSum(left + 1, right + 1);
  }
}

Fenwick Tree with Range Updates

class FenwickTreeRangeUpdate {
  private bit1: FenwickTree;
  private bit2: FenwickTree;
  private n: number;

  constructor(n: number) {
    this.n = n;
    this.bit1 = new FenwickTree(n);
    this.bit2 = new FenwickTree(n);
  }

  // O(log n) - Add value to range [left, right]
  rangeUpdate(left: number, right: number, value: number): void {
    this.bit1.update(left, value);
    this.bit1.update(right + 1, -value);
    this.bit2.update(left, value * (left - 1));
    this.bit2.update(right + 1, -value * right);
  }

  // O(log n) - Get prefix sum [1, i]
  prefixSum(i: number): number {
    return this.bit1.prefixSum(i) * i - this.bit2.prefixSum(i);
  }

  // O(log n) - Get range sum [left, right]
  rangeSum(left: number, right: number): number {
    return this.prefixSum(right) - this.prefixSum(left - 1);
  }
}

2D Fenwick Tree

class FenwickTree2D {
  private tree: number[][];
  private rows: number;
  private cols: number;

  constructor(rows: number, cols: number) {
    this.rows = rows;
    this.cols = cols;
    this.tree = Array.from({ length: rows + 1 }, () =>
      new Array(cols + 1).fill(0)
    );
  }

  private lowbit(i: number): number {
    return i & (-i);
  }

  // O(log(n) * log(m)) - Update point (r, c)
  update(r: number, c: number, delta: number): void {
    for (let i = r; i <= this.rows; i += this.lowbit(i)) {
      for (let j = c; j <= this.cols; j += this.lowbit(j)) {
        this.tree[i][j] += delta;
      }
    }
  }

  // O(log(n) * log(m)) - Prefix sum from (1,1) to (r,c)
  prefixSum(r: number, c: number): number {
    let sum = 0;
    for (let i = r; i > 0; i -= this.lowbit(i)) {
      for (let j = c; j > 0; j -= this.lowbit(j)) {
        sum += this.tree[i][j];
      }
    }
    return sum;
  }

  // O(log(n) * log(m)) - Rectangle sum from (r1,c1) to (r2,c2)
  rectangleSum(r1: number, c1: number, r2: number, c2: number): number {
    return (
      this.prefixSum(r2, c2) -
      this.prefixSum(r1 - 1, c2) -
      this.prefixSum(r2, c1 - 1) +
      this.prefixSum(r1 - 1, c1 - 1)
    );
  }
}

// Usage
const bit2d = new FenwickTree2D(4, 4);
// Add values
bit2d.update(1, 1, 1);
bit2d.update(2, 2, 2);
bit2d.update(3, 3, 3);
console.log(bit2d.rectangleSum(1, 1, 3, 3)); // 6

Time Complexity

OperationTimeSpace
BuildO(n log n)O(n)
Point UpdateO(log n)-
Prefix SumO(log n)-
Range SumO(log n)-
Range Update (special)O(log n)O(n) extra

Classic Applications

Count Inversions

function countInversions(arr: number[]): number {
  // Coordinate compression
  const sorted = [...arr].sort((a, b) => a - b);
  const rank = new Map(sorted.map((v, i) => [v, i + 1]));
  const n = arr.length;

  const bit = new FenwickTree(n);
  let inversions = 0;

  // Process from right to left
  for (let i = n - 1; i >= 0; i--) {
    const r = rank.get(arr[i])!;
    // Count elements smaller than current that we've seen
    inversions += bit.prefixSum(r - 1);
    // Mark current element as seen
    bit.update(r, 1);
  }

  return inversions;
}

console.log(countInversions([2, 4, 1, 3, 5])); // 3 (pairs: (2,1), (4,1), (4,3))

Range Sum Queries - Mutable

class NumArray {
  private bit: FenwickTree;
  private nums: number[];

  constructor(nums: number[]) {
    this.nums = [...nums];
    this.bit = new FenwickTree(nums);
  }

  update(index: number, val: number): void {
    const delta = val - this.nums[index];
    this.nums[index] = val;
    this.bit.update(index + 1, delta);
  }

  sumRange(left: number, right: number): number {
    return this.bit.rangeSum(left + 1, right + 1);
  }
}

const numArray = new NumArray([1, 3, 5]);
console.log(numArray.sumRange(0, 2)); // 9
numArray.update(1, 2);
console.log(numArray.sumRange(0, 2)); // 8

Count of Range Sum

function countRangeSum(nums: number[], lower: number, upper: number): number {
  const prefixSums: number[] = [0];
  for (const num of nums) {
    prefixSums.push(prefixSums[prefixSums.length - 1] + num);
  }

  // Sort and compress coordinates
  const sorted = [...new Set([
    ...prefixSums,
    ...prefixSums.map(p => p - lower),
    ...prefixSums.map(p => p - upper)
  ])].sort((a, b) => a - b);

  const rank = new Map(sorted.map((v, i) => [v, i + 1]));
  const bit = new FenwickTree(sorted.length);

  let count = 0;

  for (const prefix of prefixSums) {
    // Count prefix sums in range [prefix - upper, prefix - lower]
    const lo = rank.get(prefix - upper)!;
    const hi = rank.get(prefix - lower)!;
    count += bit.rangeSum(lo, hi);
    bit.update(rank.get(prefix)!, 1);
  }

  return count;
}

Fenwick Tree vs Segment Tree

AspectFenwick TreeSegment Tree
Spacen + 12n to 4n
Code complexitySimpleMore complex
Prefix queriesYesYes
Arbitrary rangeVia prefix differenceDirect
Range updatesWith modificationWith lazy propagation
Min/Max queriesLimited*Yes
FlexibilitySum, XOR operationsAny associative operation

*Fenwick tree works best with invertible operations (sum, XOR)

When to Use Fenwick Trees

Use Fenwick trees when:

  • Need prefix sum queries with point updates
  • Operations are invertible (sum, XOR)
  • Want simpler code than segment trees
  • Memory is a concern
  • Competitive programming (quick to implement)

Use Segment Trees instead when:

  • Need min/max queries
  • Need arbitrary range queries directly
  • Need range updates with lazy propagation
  • Operations are not invertible
  • Segment Trees - More flexible, handles more operations
  • Arrays - O(n) query, O(1) update baseline
  • Prefix Sum Array - O(1) query, O(n) update (static)
  • Sparse Table - O(1) query for min/max (static)