Quant Memo
Core

Fenwick Trees and Binary Indexed Trees

A Fenwick tree (binary indexed tree) answers "what's the running total up to index i" and "add x at index i" in logarithmic time, using a plain array and bit tricks instead of pointers.

Prerequisites: Big-O Complexity, Arrays and Two Pointers

You are streaming trade prints and need, at any moment, the running volume traded up to a given price level, while new prints keep arriving out of order. A plain running sum handles new prints in O(1)O(1) but a prefix query means re-summing an array in O(n)O(n). A Fenwick tree, also called a binary indexed tree (BIT), does both updates and prefix sums in O(logn)O(\log n), using nothing but an array.

The idea: each index owns a range determined by its lowest set bit

Instead of one array cell per raw value, a Fenwick tree stores partial sums, where cell ii covers a range of length equal to the lowest set bit of ii (in binary). Index 6 (110 in binary) has lowest set bit 2, so cell 6 covers indices 5–6. Index 8 (1000) has lowest set bit 8, so cell 8 covers indices 1–8. This layout is what makes both operations cheap:

  • Update index ii: add the delta to cell ii, then move to i+(i&i)i + (i \mathbin{\&} -i) — jump to the next cell whose range includes ii — and repeat until past the array end.
  • Query prefix sum up to ii: read cell ii, then move to i(i&i)i - (i \mathbin{\&} -i) — strip off the lowest set bit — and repeat until ii reaches 0, summing as you go.

i & -i isolates the lowest set bit using two's-complement arithmetic; that one expression is the entire trick. Each operation touches at most log2n\log_2 n cells, because each step either sets a new bit or clears one, and an nn-bit number has at most log2n\log_2 n bits.

1 2 3 4 5 6 7 8 covers 1 covers 1-2 covers 3 covers 1-4 covers 5 covers 5-6 covers 7 covers 1-8 query(8) jumps 8 → 0, done in one hop
Cell 8 alone answers a prefix sum over all 8 elements; cell 6 covers only indices 5-6. The range width is always the lowest set bit of the index.

Worked example

Start with all zeros over 8 slots. update(5, +3): add 3 to cell 5, jump to 5+(5&5)=5+1=65 + (5 \mathbin{\&} -5) = 5+1=6, add 3 to cell 6, jump to 6+2=86+2=8, add 3 to cell 8, jump to 16 (past the end), stop. Now query(6): read cell 6 (3), jump to 62=46-2=4, cell 4 is 0, jump to 44=04-4=0, stop. Prefix sum up to 6 is 3 — correct, since only index 5 has a nonzero value and 565 \le 6.

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

    def update(self, i, delta):        # O(log n)
        while i < len(self.tree):
            self.tree[i] += delta
            i += i & (-i)

    def query(self, i):                # prefix sum [1..i], O(log n)
        s = 0
        while i > 0:
            s += self.tree[i]
            i -= i & (-i)
        return s

A Fenwick tree trades the generality of a segment tree for a much simpler implementation: no recursion, no pointers, just one array and the i & -i bit trick. Use it whenever the operation is a prefix sum (or any invertible, associative combine) plus point updates.

Range sum on [l, r] is query(r) - query(l-1). To support range updates with point queries, apply the same trick to a difference array instead of the raw values.

Where it shows up

Interview framing: "range sum query, mutable" (LeetCode 307), counting inversions in an array, and order-statistics variants (count of elements less than xx seen so far). On a desk, a Fenwick tree over price levels tracks cumulative order-book depth as orders arrive and cancel, and over time buckets it computes running P&L or volume-weighted stats without rescanning history on every tick.

Related concepts

Practice in interviews

Further reading

  • Fenwick, A New Data Structure for Cumulative Frequency Tables (1994)
  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 14, problem set)
ShareTwitterLinkedIn