Fenwick Tree
A Fenwick tree (also called a Binary Indexed Tree, abbreviated BIT) is a compact data structure that supports two operations on an array
A[1..n]of invertible aggregates (most commonly: sums) inO(log n)each: point update (A[i] += delta) and prefix-sum query (sum(A[1..i])). A range querysum(A[l..r])follows from two prefix queries:prefix(r) − prefix(l - 1). The structure was published by Peter Fenwick in 1994 in Software: Practice and Experience — the original motivation was building cumulative-frequency tables for arithmetic coding compression — and the entire data structure rests on one bit-manipulation trick:i & -iextracts the lowest set bit ofi. Compared to a Segment Tree, a Fenwick tree uses half the memory (nslots vs4n), is dramatically simpler to code (a couple ofwhileloops, no recursion), but supports a strictly narrower class of operations: only invertible aggregates (sum, XOR), only prefix-style queries (no general range-min/max). When your problem fits, Fenwick is the right hammer; when it doesn’t, fall back to Segment Tree.
1. Intuition — A Hierarchy of Cumulative Buckets
Imagine you’re tracking a running total of donations to a charity, indexed by donor ID 1..n. You want fast “total donated by donors 1..i” answers, and you also want to be able to update a single donor’s donation cheaply.
Strategy A: maintain the array directly. Update is O(1) (just write to A[i]). But prefix(i) requires summing A[1] + A[2] + … + A[i] = O(i) = O(n) worst case.
Strategy B: maintain prefix sums directly (P[i] = A[1] + … + A[i]). Now prefix(i) = P[i] is O(1). But updating A[i] requires updating every P[j] for j ≥ i — O(n) worst case.
Both strategies are dominated, in different directions, by O(n). The Fenwick tree is the balanced compromise: every operation O(log n). The structural idea is to not store all n prefix sums, and not store the raw array, but to store a clever subset of partial sums such that any prefix can be reconstructed by summing O(log n) of them, and any single update touches O(log n) of them.
The “clever subset” is: each BIT[i] stores a sum over a range whose length equals the lowest set bit of i. Specifically:
BIT[i] = A[i − lowbit(i) + 1] + A[i − lowbit(i) + 2] + … + A[i]
where lowbit(i) = i & -i (more on this in §3). For example with n = 8:
i (decimal) | i (binary) | lowbit(i) | range covered by BIT[i] |
|---|---|---|---|
| 1 | 0001 | 1 | A[1] |
| 2 | 0010 | 2 | A[1..2] |
| 3 | 0011 | 1 | A[3] |
| 4 | 0100 | 4 | A[1..4] |
| 5 | 0101 | 1 | A[5] |
| 6 | 0110 | 2 | A[5..6] |
| 7 | 0111 | 1 | A[7] |
| 8 | 1000 | 8 | A[1..8] |
Index 4 covers the four elements ending at position 4 (i.e., A[1..4]); index 2 covers A[1..2]; index 8 covers all eight. Index 5 only covers A[5] because lowbit(5) = 1. The lengths form a perfect “binary forest” — see §8.
To compute prefix(7): walk down 7 → 6 → 4 → 0. That’s BIT[7] + BIT[6] + BIT[4] = A[7] + A[5..6] + A[1..4] = A[1..7]. ✓ Three lookups for n = 8; in general, O(log n).
To update A[5] += δ: walk up 5 → 6 → 8 → .... Add δ to BIT[5], BIT[6], BIT[8]. Three updates for n = 8; in general, O(log n).
The walk-down step replaces i with i - lowbit(i). The walk-up step replaces i with i + lowbit(i). That is the entire data structure.
2. Tiny Worked Example
Let A = [3, 2, −1, 6, 5, 4, −3, 3] (1-indexed; A[1] = 3, …, A[8] = 3).
Build the BIT. A naive build calls update(i, A[i]) for each i, costing O(n log n). A linear-time build (Θ(n)) initializes BIT[i] = A[i], then for each i in increasing order pushes BIT[i]’s value up to its “parent” i + lowbit(i) if that’s ≤ n. Either way:
BIT[1] = A[1] = 3
BIT[2] = A[1] + A[2] = 5
BIT[3] = A[3] = -1
BIT[4] = A[1] + A[2] + A[3] + A[4] = 10
BIT[5] = A[5] = 5
BIT[6] = A[5] + A[6] = 9
BIT[7] = A[7] = -3
BIT[8] = A[1] + … + A[8] = 19
Query prefix(6). Walk: 6 → 6 - lowbit(6) = 6 - 2 = 4 → 4 - lowbit(4) = 4 - 4 = 0. Stop. Sum: BIT[6] + BIT[4] = 9 + 10 = 19. Manual: 3 + 2 + (-1) + 6 + 5 + 4 = 19. ✓
Query prefix(7). Walk: 7 → 7 - 1 = 6 → 6 - 2 = 4 → 4 - 4 = 0. Sum: BIT[7] + BIT[6] + BIT[4] = -3 + 9 + 10 = 16. Manual: 3 + 2 - 1 + 6 + 5 + 4 - 3 = 16. ✓
Range query sum(A[3..6]). Compute prefix(6) - prefix(2) = 19 - (BIT[2]) = 19 - 5 = 14. Manual: -1 + 6 + 5 + 4 = 14. ✓
Update A[3] += 10 (so A[3] becomes 9). Walk up: 3 → 3 + lowbit(3) = 3 + 1 = 4 → 4 + 4 = 8 → 8 + 8 = 16 > n, stop. Add 10 to BIT[3], BIT[4], BIT[8]:
BIT[3] = -1 + 10 = 9
BIT[4] = 10 + 10 = 20
BIT[8] = 19 + 10 = 29
Re-query prefix(7): BIT[7] + BIT[6] + BIT[4] = -3 + 9 + 20 = 26. Manual: 3 + 2 + 9 + 6 + 5 + 4 - 3 = 26. ✓
The walk in both directions touches at most ⌊log₂ n⌋ + 1 indices.
3. The i & -i Bit Trick — Symbol-by-Symbol
This is the move that makes the whole structure work. We use two’s-complement integer representation throughout (all modern hardware).
Claim. For any positive integer i, the expression i & -i (bitwise AND of i with its arithmetic negation) returns an integer whose binary representation has a single 1 bit, located at the position of the lowest set bit of i (equivalently, the largest power of 2 that divides i).
Why. In two’s complement, -i = (~i) + 1 — flip all the bits, then add 1. Walk through what happens:
- Flipping
~iturns every0ofiinto1and vice versa. - Adding
1to~ithen propagates a carry from the lowest bit. The carry ripples through any trailing1s in~i(which were trailing0s ini), turning them back to0, and finally lands at the first0in~ifrom the right, which is the first1inifrom the right — i.e., the lowest set bit ofi. After the add, the lowest set bit ofiis now a1in-i; everything below it is0(because it was0ini, became1in~i, then got carried-over back to0). Everything above the lowest set bit ofiis unchanged in absolute terms —~iflipped them, and the carry didn’t reach that high; but we’re looking at~i + 1, so the high bits of-iare exactly the bit-flipped high bits ofi.
So, denoting the lowest set bit of i at position k:
- Bits below
kini:0s; in-i:0s. AND:0. - Bit at
kini:1; in-i:1. AND:1. - Bits above
kini: arbitrary; in-i: bit-flipped. AND of a bit with its flip:0.
Therefore i & -i has exactly one 1, at position k. ∎
Concrete examples.
i (decimal) | i (8-bit binary) | ~i | ~i + 1 = -i | i & -i | lowbit |
|---|---|---|---|---|---|
| 1 | 00000001 | 11111110 | 11111111 | 00000001 | 1 |
| 2 | 00000010 | 11111101 | 11111110 | 00000010 | 2 |
| 3 | 00000011 | 11111100 | 11111101 | 00000001 | 1 |
| 4 | 00000100 | 11111011 | 11111100 | 00000100 | 4 |
| 5 | 00000101 | 11111010 | 11111011 | 00000001 | 1 |
| 6 | 00000110 | 11111001 | 11111010 | 00000010 | 2 |
| 8 | 00001000 | 11110111 | 11111000 | 00001000 | 8 |
| 12 | 00001100 | 11110011 | 11110100 | 00001100 | 4 |
So lowbit(12) = 4, meaning a Fenwick index of 12 covers a range of length 4 ending at index 12, i.e., A[9..12].
Why the trick matters.
- Walk-up (update):
i ← i + lowbit(i)jumps to the “parent” Fenwick index — the next ancestor whose range coversA[i]. - Walk-down (prefix query):
i ← i - lowbit(i)strips the lowest set bit, jumping to the previous “sibling” range to be summed. - Both walks halve a meaningful quantity each step (the number of set bits between
iand the boundary), so they terminate inO(log n)iterations.
In Python the & and unary minus work on arbitrary integers; in C/C++ on int you get the same behavior because int is two’s complement. In a language without two’s-complement (rare) you’d write i & (i ^ (i - 1)) instead.
4. Pseudocode
By convention Fenwick trees are 1-indexed — the math breaks at i = 0 because lowbit(0) = 0 and 0 + 0 is a fixed point. The internal array has size n + 1 and slot 0 is unused.
update(i, delta): # A[i] += delta
while i <= n:
BIT[i] += delta
i += i & -i # walk up
prefix(i): # returns sum(A[1..i])
s = 0
while i > 0:
s += BIT[i]
i -= i & -i # walk down
return s
range_sum(l, r):
return prefix(r) - prefix(l - 1)
build_linear(A):
BIT[1..n] = A[1..n]
for i = 1 to n:
j = i + (i & -i)
if j <= n:
BIT[j] += BIT[i]
The linear build is Θ(n) because each index pushes its accumulated value to exactly one parent; total work is n pushes.
5. Python Implementation
A clean class-based Fenwick tree:
class FenwickTree:
"""1-indexed Fenwick tree (a.k.a. Binary Indexed Tree)
supporting point-update and prefix-sum query in O(log n)."""
def __init__(self, n_or_data):
if isinstance(n_or_data, int):
self.n = n_or_data
self.bit = [0] * (self.n + 1) # index 0 unused
else:
data = n_or_data
self.n = len(data)
self.bit = [0] + list(data) # copy into 1-indexed slots
for i in range(1, self.n + 1): # linear build
j = i + (i & -i)
if j <= self.n:
self.bit[j] += self.bit[i]
def update(self, i, delta):
"""Add `delta` to A[i]. 1-indexed."""
while i <= self.n:
self.bit[i] += delta
i += i & -i
def prefix(self, i):
"""Return sum(A[1..i]). 1-indexed; prefix(0) = 0."""
s = 0
while i > 0:
s += self.bit[i]
i -= i & -i
return s
def range_sum(self, l, r):
"""Return sum(A[l..r]). 1-indexed, inclusive."""
return self.prefix(r) - self.prefix(l - 1)Usage matching §2:
A = [3, 2, -1, 6, 5, 4, -3, 3]
ft = FenwickTree(A)
ft.prefix(6) # → 19
ft.prefix(7) # → 16
ft.range_sum(3, 6) # → 14
ft.update(3, 10) # A[3] += 10
ft.prefix(7) # → 26If your problem is naturally 0-indexed (e.g., LeetCode), wrap the public methods to add +1 to incoming indices — don’t try to make the BIT itself 0-indexed; the i & -i trick depends on i ≥ 1.
6. Complexity
Time.
update:Θ(log n). Proof:i + (i & -i)strictly increasesiand at least doubles its lowest-set-bit position; after at most⌊log₂ n⌋ + 1iterations,iexceedsnand the loop halts.prefix:Θ(log n). Proof:i - (i & -i)strictly decreasesiby stripping its lowest set bit. The number of set bits iniis at most⌊log₂ n⌋ + 1, so the loop runs that many times.range_sum:Θ(log n). Twoprefixcalls.build_linear:Θ(n). Each index doesO(1)push.
Space. Θ(n). One array of n + 1 slots, vs 4n for Segment Tree. Roughly half the memory in practice.
7. Variants and Use Cases
7.1 Range Update + Point Query (via difference array)
Define D as the difference array of A: D[i] = A[i] - A[i-1]. Then A[i] = D[1] + D[2] + … + D[i] = prefix_D(i). A range update A[l..r] += k corresponds to two point updates on D: D[l] += k and D[r+1] -= k. So a Fenwick tree built over D supports range-add updates and point-queries in O(log n) each — and uses only one BIT, half the work of a Segment Tree with Lazy Propagation for the same operation pair.
7.2 Range Update + Range Query (two BITs)
A clever extension by Mishra & Sahni (folklore in CP) supports range-add + range-sum in O(log n) with two Fenwick trees, by maintaining the “linear part” and “constant part” of the running sum separately. The math: if you’ve performed range-adds on [l₁, r₁], [l₂, r₂], … with deltas k_j, then prefix(i) = Σ k_j × (i - l_j + 1) for the segments fully covering up to i, plus partial contributions. Reformulating: prefix(i) = i × Σ k_j - Σ k_j × (l_j - 1). Maintain two BITs — one for Σ k_j and one for Σ k_j × (l_j - 1) — and you can answer prefix(i) in O(log n). Implementation is short but tricky; cp-algorithms has a clean writeup.
7.3 2-D Fenwick Tree
A BIT of BITs. BIT2D[i][j] indexes a 2-D rectangle [1..i] × [1..j]. Update and query both cost O(log n × log m). Memory is O(nm). Used for 2-D range-sum problems with point updates (LC 308 Range Sum Query 2D — Mutable in O(n m + q log n log m) instead of segment-tree-of-segment-trees’ O(n m + q log² n)).
7.4 BIT for min / max — NO
This is one of the most-asked Stack-Overflow questions about BITs and the answer is you can’t, in general. Sum is invertible: prefix(r) - prefix(l - 1) = sum(A[l..r]). Min and max are not — there’s no way to recover min(A[l..r]) from min(A[1..r]) and min(A[1..l-1]). You can do “prefix-min queries” with a BIT (just replace += with min), but only if the only update operation is “set A[i] to a value smaller than its current value” — once decreases-only is broken, the structure breaks. Use a Segment Tree for general range-min/max with point updates.
7.5 BIT for XOR
XOR is its own inverse (a XOR a = 0), so a Fenwick tree storing XOR aggregates supports range_xor(l, r) = prefix_xor(r) XOR prefix_xor(l - 1) cleanly.
7.6 Order-Statistic BIT (find-the-k-th)
If A[i] is a frequency count of value i (0 or 1 in the simplest case), a BIT can answer “find the smallest index j such that prefix(j) ≥ k” — i.e., the k-th order statistic — in O(log n) using a top-down walk. This is the binary lifting on a BIT trick; foundational for LC 315 Count of Smaller Numbers After Self, LC 493 Reverse Pairs, and the offline/online algorithms for “median of a stream” with bounded values.
7.7 Production
BITs occasionally appear in production code where:
- A small fixed-size table needs
O(log n)cumulative-frequency updates (e.g., adaptive arithmetic coders — Fenwick’s original use case). - A column store maintains running totals under occasional updates (Apache Druid has historically used a BIT-like structure for some aggregator chains).
- Real-time analytics dashboards over very small dimensions.
For larger production workloads, the LSM tree, B-tree, and skiplist dominate.
8. Pitfalls
- Off-by-one from forgetting 1-indexing. Fenwick is 1-indexed in the math. If your input array is 0-indexed (Python, LeetCode), wrap or shift indices on entry. A common bug: calling
prefix(0)and getting0correctly, then accidentally queryingprefix(-1)from a translated input — infinite loop becausei > 0never becomes false from a negative. - Using a BIT for non-invertible aggregates. As covered in §7.4. If your problem requires range-min/max with arbitrary updates, BIT silently produces wrong answers. Use a Segment Tree.
- Forgetting that range query needs
prefix(l - 1), notprefix(l).prefix(r) - prefix(l - 1)coversA[l..r]inclusive. Off-by-one bugs in this subtraction are easy. - Misimplementing
lowbit. In some languages or withunsigned int,-idoesn’t work as expected. In C, applying unary-to anunsignedproduces a defined wraparound result that also gives the right bit pattern, but usingi & (~i + 1)is more portable. - Initializing with the slow
n-update build. The naive build callsupdatefor every element —O(n log n). The linear-time build (§4) isO(n)and worth using whennis large. Some contest solutions get TLE specifically because the build wasO(n log n). - Mixing 0-indexed array and 1-indexed BIT. If you manage both, document which is which — it is very easy to update the wrong index by 1 and end up with a BIT that’s correct almost everywhere.
- Trying to do “set A[i] = v” instead of “A[i] += delta”. BIT primitives are additive. To set
A[i] = v, you computedelta = v - current_A_at_iand callupdate(i, delta). To get the current value, queryrange_sum(i, i). This isO(log n)rather thanO(1), but unavoidable. - Recursion not used — but the iterative loop has a subtle infinite-loop trap. If
iis0and you writei -= i & -i, you get0 - 0 = 0. Always guard withwhile i > 0. Symmetrically, inupdate, never call withi = 0.
9. Diagram
flowchart TD BIT8["BIT[8] : A[1..8]"] BIT4["BIT[4] : A[1..4]"] BIT2["BIT[2] : A[1..2]"] BIT1["BIT[1] : A[1]"] BIT3["BIT[3] : A[3]"] BIT6["BIT[6] : A[5..6]"] BIT5["BIT[5] : A[5]"] BIT7["BIT[7] : A[7]"] BIT8 --> BIT4 BIT8 --> BIT6 BIT8 --> BIT7 BIT4 --> BIT2 BIT4 --> BIT3 BIT2 --> BIT1 BIT6 --> BIT5
What this diagram shows. This is the implicit “Fenwick forest” for n = 8. Each node is a Fenwick array slot, labeled with the array range it summarizes. Edges go parent → child, where “parent” is i and “children” are the indices j such that j + lowbit(j) = i and j < i. Equivalently, the children of i are the indices reachable by stripping bits in the prefix-query walk: the descendants of BIT[i] are exactly the slots a prefix(i) walk would touch on its way down. The structure is not a single tree — it’s a forest of O(log n) trees rooted at the powers of 2; the “missing root” connecting BIT[8] to no parent simply means 8 + lowbit(8) = 16 > n and the walk-up stops. The depth of the deepest tree is ⌊log₂ n⌋ + 1, which gives the O(log n) operation bound. Notice that BIT[5], BIT[6] form their own little subtree — 5’s parent is 6, 6’s parent is 8 — and that BIT[7] sits alone with no children because no j < 7 has j + lowbit(j) = 7 (you can verify: j = 6 ⇒ 6 + 2 = 8 ≠ 7; no such j exists).
10. Common Interview Problems
| LC # | Problem | Why Fenwick |
|---|---|---|
| 307 | Range Sum Query — Mutable | Canonical point-update + prefix-sum |
| 308 | Range Sum Query 2D — Mutable | 2-D Fenwick tree |
| 315 | Count of Smaller Numbers After Self | Coordinate-compressed Fenwick + count queries |
| 327 | Count of Range Sum | Fenwick on cumulative-sum-rank-compressed array |
| 493 | Reverse Pairs | Same idea as 315 with a different comparison |
| 1395 | Count Number of Teams | Fenwick over rank-compressed ratings |
| 2179 | Count Good Triplets in an Array | Two Fenwicks counting “left less” and “right greater” |
| 1409 | Queries on a Permutation With Key | Order-statistic Fenwick |
| 2031 | Count Subarrays With More Ones Than Zeros | Fenwick for prefix-sum frequencies |
| 1505 | Minimum Possible Integer After at Most K Adjacent Swaps | Fenwick of “how many original positions are still unused before me” |
A useful interview heuristic: when the problem screams “count inversions / count pairs with property P / k-th smallest dynamically”, a Fenwick tree (often after coordinate compression) is the cleanest tool.
11. Open Questions
- How much of the practical advantage of Fenwick over Segment Tree is the constant factor (cache locality of one tight array vs. recursion overhead) versus the lower memory? Microbenchmarks vary; cp-algorithms claims ~2x in CP-style workloads.
- Can you “lazy-propagate” range updates in a Fenwick tree without the two-BIT trick? The literature says no for general aggregates; the two-BIT decomposition is currently the best known for sum.
- Is there a known information-theoretic argument that an
O(log n)-per-op data structure cannot do better than ~nwords of memory for cumulative frequencies? Fenwick uses exactlyn; the question is whethern - O(log n)is achievable. (To my knowledge: no — but I haven’t found a tight bound in the literature.)
12. See Also
- Segment Tree — sibling; more general (any associative aggregate), uses 4× the memory
- Segment Tree with Lazy Propagation — sibling; for full range-update + range-query
- Sparse Table — sibling;
O(1)queries when no updates - Difference Arrays — the trick that turns range-update + point-query into point-update + prefix-query for a Fenwick tree
- Prefix Sums — the static read-only ancestor of all of these
- Binary Heap — same array indexing pattern (different math)
- Big-O Notation
- SWE Interview Preparation MOC