Fenwick Trees (BIT)
A tiny array that answers prefix sums and point updates in O(log n) by exploiting the binary representation of indices.
Prerequisites
- Big-O & Asymptotic Analysis — we're chasing
O(log n)on both operations again. - Arrays — the entire structure is one small 1-indexed array.
- Prefix Sums & Difference Arrays — the
O(1)query /O(n)update tradeoff we want to beat. - Bit Manipulation & Bitmask DP — the low-bit trick
i & -iis the beating heart of this page.
The 10-second version
A Fenwick tree — also called a Binary Indexed Tree (BIT) — keeps cumulative sums
available for both fast queries and fast updates, using a single array of the same size as
the data. Each slot i secretly owns a small block of elements: exactly the block of length
lowbit(i) = i & -i that ends at i. To read a prefix sum you walk down the array,
stripping one set bit at a time; to apply an update you walk up, adding the low bit each
step. Both walks are O(log n), both use O(n) memory, and the whole thing is about five
lines of code.
Why this existed
The story starts with a task that sounds boring but shows up everywhere in signal processing
and compression: maintain a cumulative frequency table. You have a stream of symbols, you
keep a running count for each one, and you constantly need to ask "how many symbols so far
are ≤ x?" — the prefix sum — while also bumping individual counts as new symbols arrive.
There were two obvious ways to hold this, and each was half-broken:
- Keep the raw counts. Updating is
O(1), but a prefix query has to add up everything belowx—O(n). - Keep a running prefix table. Querying is
O(1), but the moment one count changes, every prefix after it is now wrong — you rebuild inO(n).
Compression algorithms like arithmetic coding hit both operations constantly and fast, so neither "half-broken" option was acceptable.
- 1980s–early 1990s — cumulative frequency tables are a bottleneck in arithmetic coding: each encoded symbol needs both an update and a prefix lookup, millions of times.
- 1994 — Peter Fenwick publishes "A New Data Structure for Cumulative Frequency
Tables" in Software: Practice and Experience, giving a structure with
O(log n)for both operations and a footprint of one array. - 1990s–2000s — the structure is rediscovered and popularized under the name Binary Indexed Tree; it becomes a staple of competitive programming for range sums, counting inversions, and order statistics.
Why it mattered then: a compression codec that spends O(n) per symbol is useless — the
table maintenance would dwarf the compression. Cutting maintenance to O(log n) with almost
no memory made the whole approach practical. And it did it with integers, not pointers — a
perfect fit for the machines of the day.
Inside the inventor's head
Fenwick's instinct was to stop treating the array as a flat sequence of positions and start
treating the index itself as information. Here's the leap: an index like 12 isn't just
"the 12th slot." It's a binary number, 1100, and the position of its lowest set bit says
how much work that slot should be responsible for.
Walk the reasoning:
The classic first attempt is to stash a partial sum at every power-of-two boundary. That works, but it's clumsy and full of special cases. The pivot is realizing that every index, not just the powers of two, can own a block — the block size being its lowest set bit. That one generalization makes the query and update walks perfectly symmetric and case-free.
Here's the trap and the fix, side by side. Notice how the naive version has to touch a whole tail of the array on an update:
// Naive: one change invalidates the whole suffix — O(n)function rebuild(a, pre) { pre[0] = a[0]; for (let i = 1; i < a.length; i++) pre[i] = pre[i - 1] + a[i];}// Fenwick: an update touches only O(log n) slots on its way upfunction update(i, delta, n, tree) { for (; i <= n; i += i & -i) tree[i] += delta;}The i & -i expression is the aha written in one line. Negating a two's-complement integer
flips every bit and adds one, which leaves exactly the lowest set bit standing: 12 is
1100, -12 is ...0100, and 12 & -12 = 4. That single value tells you how big the block
is — and, as we'll see, exactly how far to jump next.
The final approach
What each slot owns
Slot i stores the sum of the elements in the half-open range (i - lowbit(i), i]. For a
7-element array:
Big low-bit indices own wide blocks; odd indices own a single element. That's why tree[4]
can answer "sum of the first four" in one read, while tree[5] holds only a[5].
The two walks
The invariants
- Coverage.
tree[i]equals the sum of the block(i - lowbit(i), i]. Becauselowbit(i)is the largest power of two dividingi, these blocks tile the array exactly. - Query decomposition. Any prefix
1..iis the disjoint union of the blocks you meet by repeatedly stripping the lowest set bit ofi— that's just readingiin binary, bit by bit from the bottom. - Update reach. When you add
deltaat positioni, you must touch exactly the slots whose blocks containi. Those are found by repeatedly addinglowbit— each step jumps to the next larger nested block that spans the position.
Both walks move at most one set bit per step, so both are bounded by the number of bits —
O(log n).
A worked trace
Say the values are [3, 2, -1, 6, 5] at positions 1..5. Build by applying five updates:
| Action | tree after |
|---|---|
update(1, 3) | [_, 3, 3, _, 3, _] |
update(2, 2) | [_, 3, 5, _, 5, _] |
update(3, -1) | [_, 3, 5, -1, 4, _] |
update(4, 6) | [_, 3, 5, -1, 10, _] |
update(5, 5) | [_, 3, 5, -1, 10, 5] |
Now prefix(5): read tree[5] = 5, drop the low bit → 4; read tree[4] = 10, drop →
0; total 15. Check: 3 + 2 + (-1) + 6 + 5 = 15. ✓ And prefix(2) reads just
tree[2] = 5, which is exactly 3 + 2. ✓
A range sum [l, r] is then prefix(r) - prefix(l - 1) — one subtraction, two walks.
It in code
class Fenwick { constructor(n) { this.n = n; this.tree = new Array(n + 1).fill(0); // 1-indexed } update(i, delta) { for (; i <= this.n; i += i & -i) this.tree[i] += delta; } prefix(i) { let sum = 0; for (; i > 0; i -= i & -i) sum += this.tree[i]; return sum; } rangeSum(l, r) { return this.prefix(r) - this.prefix(l - 1); }}Complexity
| Operation | Best | Average | Worst | Space |
|---|---|---|---|---|
| Build (n updates) | Θ(n log n) | Θ(n log n) | Θ(n log n) | Θ(n) |
| Build (linear pass) | Θ(n) | Θ(n) | Θ(n) | Θ(n) |
| Point update | Θ(1) | Θ(log n) | Θ(log n) | Θ(1) |
| Prefix query | Θ(1) | Θ(log n) | Θ(log n) | Θ(1) |
You can build in Θ(n) instead of n updates: for i from 1 to n, let j = i + lowbit(i)
and, if j ≤ n, do tree[j] += tree[i]. Each slot is filled once, so it's a single linear
pass.
Where you meet it today
- Counting inversions. "How many pairs are out of order?" reduces to, for each element,
asking the BIT for how many smaller values have already been seen —
O(n log n)total. - Order statistics. Store
1at each present value and the BIT can find thek-th smallest by descending through bit positions — a selection structure inO(log n). - Compression, the original use. Arithmetic and range coders maintain mutable cumulative frequency tables; a BIT is a natural fit.
- 2D grids. A Fenwick tree of Fenwick trees answers rectangle sums over a matrix, still in
O(log² n)— the same trick stacked.
// rank holds each value's compressed rank in 1..nfunction countInversions(rank) { const n = rank.length; const bit = new Fenwick(n); let inv = 0; for (let i = n - 1; i >= 0; i--) { inv += bit.prefix(rank[i] - 1); // smaller values seen to the right bit.update(rank[i], 1); } return inv;}Recap
- A Fenwick tree answers prefix sums and point updates in
O(log n)using one array of sizen + 1— no tree nodes, no recursion, no4noverhead. - The magic is the low-bit trick:
i & -igives the length of the block slotiowns, and it also drives both walks. - Query strips the lowest set bit (
i -= i & -i); update adds the low bit (i += i & -i). Both move one set bit per step, henceO(log n). - Range sums are two prefix queries and a subtraction; building can be done in a single
Θ(n)pass.
Next up: Knuth-Morris-Pratt — we leave the array behind and start finding one string inside another, in linear time.