What is a Fenwick Tree?
A Fenwick Tree, also known as a Binary Indexed Tree (BIT), is a data structure that efficiently supports point updates and prefix sum queries on an array of numbers. Invented by Peter M. Fenwick in 1994, it provides an elegant compromise between a plain array (fast updates, slow queries) and a prefix sum array (fast queries, slow updates). The Fenwick Tree achieves O(log n) time for both operations, making it a powerful tool for problems involving cumulative frequency counts, range sum queries, and more.
At its core, a Fenwick Tree stores partial sums in a clever way that allows both updates and queries to traverse only O(log n) nodes by exploiting the binary representation of array indices. The data structure is remarkably compact: it requires only O(n) extra space and can be implemented with just a handful of lines of code.
Key Terminology
- Point Update: Modify the value at a single index in the array
- Prefix Query: Retrieve the sum of all elements from index 1 up to a given index i
- Range Query: Retrieve the sum of elements between two indices [l, r], computed as prefix(r) - prefix(l-1)
- LSB (Least Significant Bit): The lowest set bit in a number, which governs the tree's structure
Why a Fenwick Tree Matters
๐ Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Understanding when and why to use a Fenwick Tree is critical for any developer working with cumulative or range-based data. Here are the primary reasons it matters:
The Classic Trade-off Problem
Consider an array of n elements where you need to support two operations:
- Update a single element's value
- Query the sum of a prefix of elements
With a raw array, updates take O(1) but prefix queries require O(n) to sum elements from 1 to i. With a precomputed prefix sum array, queries become O(1) but a single update forces you to recompute all prefix sums from that index onward โ an O(n) operation. The Fenwick Tree resolves this dilemma by making both operations O(log n).
Real-World Use Cases
- Competitive Programming: Fenwick Trees are ubiquitous in problems involving dynamic range queries, such as computing running medians, counting inversions, or tracking frequency distributions that change over time.
- Database Systems: Maintaining running aggregates (e.g., cumulative revenue, rolling counts) where individual records are inserted or updated.
- Financial Applications: Tracking portfolio values with frequent price updates and need for quick cumulative value snapshots.
- Game Development: Managing scoreboards where player scores change and rank queries require cumulative counts.
- Data Analysis: Efficiently computing sliding window statistics or maintaining order statistics over streaming data.
Advantages Over Segment Trees
While segment trees can handle a broader class of operations (range minimum, maximum, GCD, etc.), Fenwick Trees offer distinct advantages:
- Smaller memory footprint: Exactly n+1 integers vs. 4n for a typical segment tree
- Simpler implementation: Around 10โ15 lines of code vs. 40+ for a full segment tree
- Faster constant factors: The bit manipulation loop is extremely cache-friendly and has low overhead
- Easier to debug: Fewer edge cases and a more transparent structure
How the Fenwick Tree Works Internally
The magic of the Fenwick Tree lies in its indexing scheme. Instead of storing the actual array values, the tree stores partial sums where each node covers a range of responsibilities determined by the binary representation of its index.
The Responsibility Concept
Each index i in the Fenwick tree array (let's call it bit[i]) is responsible for storing the sum of a contiguous range of the original array ending at i. The length of this range is exactly the value of the least significant set bit of i โ denoted as LSB(i) or i & -i.
For example:
- Index 6 (binary 110) has LSB = 2 (binary 010), so bit[6] stores the sum of indices 5..6 (range length 2)
- Index 8 (binary 1000) has LSB = 8 (binary 1000), so bit[8] stores the sum of indices 1..8 (range length 8)
- Index 7 (binary 111) has LSB = 1 (binary 001), so bit[7] stores only the value at index 7
This creates a tree-like structure where each node's parent is found by adding its LSB, and its child nodes are found by removing the LSB.
Visualizing the Tree
For an array of size 8, the tree structure looks like this:
Indices: 1 2 3 4 5 6 7 8 LSB: 1 2 1 4 1 2 1 8 Covers: [1] [1..2] [3] [1..4] [5] [5..6] [7] [1..8]
Notice how index 4 (LSB=4) covers 1..4, and index 8 (LSB=8) covers the entire array. This hierarchical coverage means that any prefix sum can be constructed by combining at most O(log n) tree nodes.
The Two Core Operations Visualized
Prefix Query (sum from 1 to i): Start at i, add bit[i] to your accumulator, then repeatedly remove the LSB from i (i = i - LSB(i)) until i becomes 0. This collects exactly the non-overlapping ranges that cover the prefix.
Point Update (add value to index i): Start at i, add the value to bit[i], then repeatedly add the LSB to i (i = i + LSB(i)) until i exceeds n. This propagates the update to all nodes that "cover" index i in their range.
Implementation in Python
Below is a complete, production-ready implementation of a Fenwick Tree in Python. We'll walk through each method and explain the time complexity.
class FenwickTree:
"""
A Fenwick Tree (Binary Indexed Tree) supporting:
- Point updates: add a value to any index
- Prefix queries: sum from index 1 to i
- Range queries: sum from index l to r (inclusive)
- Value access: read the current value at an index
All operations are O(log n) where n is the array size.
"""
def __init__(self, size_or_array):
"""
Initialize the Fenwick Tree.
Args:
size_or_array: Either an integer size (for a zero-filled tree)
or a list/array of initial values.
Time Complexity: O(n) for construction from array,
O(1) for zero-initialization of given size.
"""
if isinstance(size_or_array, int):
self.n = size_or_array
self.bit = [0] * (self.n + 1) # 1-indexed internal array
self.original = [0] * (self.n + 1) # store original values
else:
arr = list(size_or_array)
self.n = len(arr)
self.bit = [0] * (self.n + 1)
self.original = [0] * (self.n + 1)
# Build tree in O(n) using prefix construction
for i in range(1, self.n + 1):
self.original[i] = arr[i - 1]
self.bit[i] = arr[i - 1]
# Propagate contributions to parent nodes
for i in range(1, self.n + 1):
parent = i + (i & -i)
if parent <= self.n:
self.bit[parent] += self.bit[i]
def _lsb(self, i):
"""Return the least significant bit of i."""
return i & -i
def update(self, idx, delta):
"""
Add 'delta' to the element at 1-based index 'idx'.
Args:
idx: 1-based index (1 <= idx <= n)
delta: value to add (can be negative)
Time Complexity: O(log n)
"""
if idx < 1 or idx > self.n:
raise IndexError(f"Index {idx} out of bounds [1, {self.n}]")
# Track original value for read operations
self.original[idx] += delta
i = idx
while i <= self.n:
self.bit[i] += delta
i += self._lsb(i) # Move to next responsible parent
def set_value(self, idx, new_value):
"""
Set the element at 1-based index 'idx' to 'new_value'.
Time Complexity: O(log n) โ performs an update with the difference.
"""
current = self.original[idx]
delta = new_value - current
self.update(idx, delta)
def prefix_sum(self, idx):
"""
Return sum of elements from index 1 to 'idx' (inclusive).
Returns 0 if idx is 0 or negative.
Time Complexity: O(log n)
"""
if idx <= 0:
return 0
if idx > self.n:
idx = self.n
total = 0
i = idx
while i > 0:
total += self.bit[i]
i -= self._lsb(i) # Remove LSB to move to next covering range
return total
def range_sum(self, left, right):
"""
Return sum of elements from 'left' to 'right' (1-based, inclusive).
Time Complexity: O(log n)
"""
if left < 1 or right > self.n or left > right:
raise IndexError(f"Invalid range [{left}, {right}] for size {self.n}")
return self.prefix_sum(right) - self.prefix_sum(left - 1)
def get_value(self, idx):
"""
Return the current value at 1-based index 'idx'.
Time Complexity: O(log n) โ can be O(1) if tracking original array.
With our 'original' array tracking, this is O(1).
"""
return self.original[idx]
def __repr__(self):
values = [self.get_value(i) for i in range(1, self.n + 1)]
return f"FenwickTree({values})"
Breaking Down the Implementation
Let's examine each component in detail:
1. The LSB function: i & -i uses two's complement arithmetic to isolate the lowest set bit. For i=12 (binary 1100), -12 in two's complement is...1000100 (in 32-bit: 1111...11110100), and the bitwise AND yields 4 (binary 0100). This is the core mechanism that governs tree navigation.
2. Construction: The constructor handles two cases. When given a plain integer, it allocates a zero-filled tree. When given an array, it builds the tree in O(n) time by first copying values, then propagating each node's value to its immediate parent. This is more efficient than n individual O(log n) updates, which would take O(n log n).
3. The update loop: i += i & -i climbs the tree to all ancestors that need to know about the change. For index 3 (binary 11), the path visits 3โ4โ8โ16โ... each adding the delta to their accumulated sums.
4. The prefix query loop: i -= i & -i collects disjoint ranges. For index 7 (binary 111), the path collects bit[7] (covers [7]), then i=6 collects bit[6] (covers [5..6]), then i=4 collects bit[4] (covers [1..4]), yielding the complete sum for 1..7.
Time Complexity Analysis
A rigorous analysis of Fenwick Tree operations reveals why it's so efficient. Let n be the size of the array.
Update Operation: O(log n)
In each iteration of the update loop, we add the LSB to the current index. This operation clears the lowest set bit and may carry to a higher bit. The number of iterations equals the number of times we can add the LSB before exceeding n. In the worst case (starting from index 1), the path visits indices: 1 (LSB=1), 2 (LSB=2), 4 (LSB=4), 8 (LSB=8), 16, 32, ... until exceeding n. This is exactly the number of bits in n โ that is, O(logโ n) iterations. Each iteration does constant work (one addition and one bit operation), so the total time is O(log n).
In the best case (starting from a power of 2 like index 8 with n=16), only one or two iterations are needed. But asymptotically, the worst case dominates.
Prefix Query Operation: O(log n)
Each iteration removes the LSB from i. For any integer i, the number of set bits is at most logโ n. Starting from i, we strip off the lowest set bit one at a time until i reaches 0. In the worst case (i = 2^k - 1, a number with all lower bits set, like 7 = 111โ or 15 = 1111โ), we need k = O(log n) iterations. Thus, prefix queries also run in O(log n) time.
Range Query: O(log n)
A range query simply performs two prefix queries and subtracts them. This takes 2ยทO(log n) = O(log n) time.
Construction from Array: O(n)
The optimized constructor builds the tree in linear time. It first copies all n values, then iterates once through indices 1..n, propagating each node's value to its parent. Each node has exactly one immediate parent (i + LSB(i)), and the propagation is a single addition. This takes exactly n - 1 parent propagations (since the root at the largest power of 2 has no parent within n), yielding O(n) total construction time. This is superior to the naive approach of calling update() n times, which would take O(n log n).
Space Complexity: O(n)
The Fenwick Tree uses an internal array of size n+1 (1-indexed), plus optionally an array of size n+1 to track original values. This is ฮ(n) space, which is minimal for any data structure supporting these operations. In contrast, a segment tree typically requires 2n to 4n space.
Implementation in C++ (For Performance Context)
For developers working in performance-critical environments, here is a compact C++ implementation that demonstrates the same principles with minimal overhead:
#include <vector>
#include <cstdint>
class FenwickTree {
private:
int n;
std::vector<int64_t> bit;
std::vector<int64_t> original;
public:
// Constructor: O(n) build from array or O(1) for zero-init
FenwickTree(int size) : n(size), bit(size + 1, 0), original(size + 1, 0) {}
FenwickTree(const std::vector<int64_t>& arr)
: n(arr.size()), bit(arr.size() + 1, 0), original(arr.size() + 1, 0) {
// Stage 1: copy values
for (int i = 1; i <= n; ++i) {
original[i] = arr[i - 1];
bit[i] = arr[i - 1];
}
// Stage 2: propagate to parents in O(n)
for (int i = 1; i <= n; ++i) {
int parent = i + (i & -i);
if (parent <= n) {
bit[parent] += bit[i];
}
}
}
// Point update: add delta at 1-based index idx โ O(log n)
void update(int idx, int64_t delta) {
original[idx] += delta;
for (int i = idx; i <= n; i += i & -i) {
bit[i] += delta;
}
}
// Set value at idx to new_value โ O(log n)
void set(int idx, int64_t new_value) {
int64_t current = original[idx];
update(idx, new_value - current);
}
// Prefix sum [1..idx] โ O(log n)
int64_t prefix_sum(int idx) const {
if (idx <= 0) return 0;
if (idx > n) idx = n;
int64_t total = 0;
for (int i = idx; i > 0; i -= i & -i) {
total += bit[i];
}
return total;
}
// Range sum [l, r] โ O(log n)
int64_t range_sum(int l, int r) const {
return prefix_sum(r) - prefix_sum(l - 1);
}
// Get single value โ O(1)
int64_t at(int idx) const {
return original[idx];
}
};
The C++ version benefits from zero-overhead abstractions, and the bit operations compile directly to efficient CPU instructions like BLSU (x86) or RBIT/CLZ (ARM). In competitive programming environments, this implementation runs blazingly fast even for n = 10โถ with hundreds of thousands of mixed operations.
Common Operations and Their Complexity
Beyond basic point updates and prefix queries, the Fenwick Tree can be adapted to handle several other operations. Here is a summary table:
| Operation | Description | Time Complexity | Notes |
|---|---|---|---|
| Point Update | Add delta to index i | O(log n) | Core operation |
| Prefix Sum Query | Sum from 1 to i | O(log n) | Core operation |
| Range Sum Query | Sum from l to r | O(log n) | Uses 2 prefix queries |
| Value at Index | Read single value | O(1) | With separate tracking array |
| Range Update (add to range) | Add delta to all elements in [l,r] | O(log n) | Requires two BITs (difference array technique) |
| Point Query after Range Updates | Get value at i after range adds | O(log n) | Using difference array BIT |
| Find K-th element (by frequency) | Binary search on prefix sums | O(log n) or O(logยฒ n) | Useful for order statistics |
| Construction from Array | Build tree initially | O(n) | Linear time build |
Range Updates and Point Queries
By using a difference array technique with a Fenwick Tree, you can support range updates (adding a value to all elements in [l, r]) and point queries (reading the value at a single index). This requires maintaining a BIT over the difference array d[i] = a[i] - a[i-1]. Here's how:
class FenwickRangeUpdate:
"""
Supports range add updates and point queries using a difference array BIT.
"""
def __init__(self, size):
self.n = size
self.bit = [0] * (size + 2) # Extra space for range operations
def _lsb(self, i):
return i & -i
def _add(self, idx, delta):
"""Internal point update on the difference BIT."""
i = idx
while i <= self.n + 1:
self.bit[i] += delta
i += self._lsb(i)
def range_add(self, l, r, delta):
"""
Add 'delta' to all elements in 1-based range [l, r].
Time Complexity: O(log n)
"""
self._add(l, delta)
self._add(r + 1, -delta)
def point_query(self, idx):
"""
Return the value at 1-based index 'idx'.
Time Complexity: O(log n)
"""
total = 0
i = idx
while i > 0:
total += self.bit[i]
i -= self._lsb(i)
return total
This is a classic technique: adding delta to [l, r] means adding delta at position l and subtracting delta at position r+1 in the difference array. A point query then sums the difference array up to that index, which reconstructs the actual value.
Practical Examples and Use Cases
Example 1: Counting Inversions in an Array
Counting inversions (pairs i < j where arr[i] > arr[j]) is a classic problem solved efficiently with a Fenwick Tree. The approach processes elements in order while maintaining a frequency BIT of values seen so far.
def count_inversions(arr):
"""
Count inversions in an array using a Fenwick Tree.
Time Complexity: O(n log M) where M is the max value.
"""
if not arr:
return 0
# Coordinate compression: map values to ranks 1..k
sorted_unique = sorted(set(arr))
rank = {val: i + 1 for i, val in enumerate(sorted_unique)}
max_rank = len(sorted_unique)
bit = FenwickTree(max_rank)
inversions = 0
# Process array from left to right
for i, val in enumerate(arr):
r = rank[val]
# Count how many elements > current have been seen
# Total seen so far = i, elements <= current = bit.prefix_sum(r)
greater_count = i - bit.prefix_sum(r)
inversions += greater_count
# Mark current element as seen
bit.update(r, 1)
return inversions
# Test
arr = [5, 3, 2, 1, 4]
print(f"Inversions in {arr}: {count_inversions(arr)}") # Output: 7
This runs in O(n log n) time after coordinate compression, dramatically faster than the O(nยฒ) naive nested loop approach.
Example 2: Dynamic Range Sum with Mixed Operations
Consider a scenario where you maintain an array of stock prices and need to answer arbitrary range sum queries while prices update in real time:
# Simulate a stock price tracker
prices = [100, 102, 98, 105, 110, 108, 112, 115]
ft = FenwickTree(prices)
# Query total value of stocks from day 2 to day 5
total = ft.range_sum(2, 5)
print(f"Sum of prices day 2-5: {total}") # 102+98+105+110 = 415
# Price drops on day 4 from 105 to 99
ft.set_value(4, 99)
print(f"After update, sum day 2-5: {ft.range_sum(2, 5)}") # 102+98+99+110 = 409
# Add a flash spike of +5 to day 3
ft.update(3, 5)
print(f"After spike, sum day 2-5: {ft.range_sum(2, 5)}") # 102+103+99+110 = 414
Example 3: Finding the K-th Smallest Element (Order Statistics)
When the Fenwick Tree stores frequencies (counts of occurrences), you can binary search on prefix sums to find the k-th smallest element in O(logยฒ n) or O(log n) time:
def find_kth_smallest(bit, k):
"""
Find the k-th smallest index (1-based) in a frequency BIT.
Assumes bit stores non-negative frequencies.
Returns the 1-based index whose prefix sum reaches k.
Time Complexity: O(logยฒ n) with binary search, or O(log n) with bit traversal.
"""
n = bit.n
if bit.prefix_sum(n) < k:
return None # Not enough elements
# Binary search on prefix sums
low, high = 1, n
while low < high:
mid = (low + high) // 2
if bit.prefix_sum(mid) >= k:
high = mid
else:
low = mid + 1
return low
# Usage: track frequencies of values
freq_bit = FenwickTree(10) # Values from 1 to 10
for val in [3, 1, 4, 1, 5, 9, 2, 6]:
freq_bit.update(val, 1)
print(f"3rd smallest: {find_kth_smallest(freq_bit, 3)}") # Should be 2
print(f"5th smallest: {find_kth_smallest(freq_bit, 5)}") # Should be 4
For a more optimized O(log n) approach, you can traverse the BIT directly using bit-level binary lifting, leveraging the tree's structure to descend from the highest power of 2.
Best Practices and Common Pitfalls
Best Practices
- Always use 1-based indexing internally. The BIT algorithm fundamentally relies on 1-indexing. If your application uses 0-indexing, add 1 when calling BIT methods and subtract 1 when reading results. This avoids off-by-one errors that are notoriously hard to debug.
- Prefer the linear-time constructor. When initializing from a known array, use the O(n) build method shown above rather than calling update() n times. The performance difference is significant: O(n) vs O(n log n). For n = 10โถ, this can mean milliseconds vs. seconds.
- Track original values separately if needed. The BIT's internal array stores partial sums, not individual values. If you need O(1) access to single elements (or need to compute a delta for set operations), maintain a separate array of original values. The small extra O(n) memory is almost always worth it.
- Use coordinate compression for large value ranges. If your values span a huge range (e.g., 1 to 10โน), compress them to ranks 1..k where k is the number of distinct values. This keeps the BIT size manageable.
- Consider the difference array trick for range updates. If you need range add + point query (or even range add + range sum with two BITs), the difference array approach elegantly extends the BIT's capabilities.
- Benchmark with realistic data sizes. While O(log n) is theoretically excellent, constant factors matter. For very small n (e.g., n < 50), a simple array and direct summation may be faster due to cache and branch prediction advantages. Profile before committing to a BIT.
Common Pitfalls
- Off-by-one errors: The most frequent bug is confusing 0-indexed input with 1-indexed BIT operations. Always validate that indices passed to update/query are in [1, n]. Write assertion checks during development.
- Forgetting that BIT stores partial sums: Attempting to read a single value by accessing bit[idx] directly will give you a partial sum (covering a range), not the individual element. Always use the prefix_sum difference or maintain a separate original array.
- Integer overflow: Sums can grow large. In C++, use
long long(int64_t) for the BIT array. In Python, integers are arbitrary precision, but performance degrades for huge numbers โ be aware. - Using BIT for non-invertible operations: Fenwick Trees work for operations that have an inverse (like sum, where subtraction reverses addition). They cannot directly handle minimum, maximum, or GCD queries because these lack an inverse operation. Use a segment tree for those.
- Inefficient updates in loops: If you need to update many indices in sequence, consider whether batch processing or a different data structure is more appropriate. Each update is O(log n), so updating all n elements is O(n log n) โ sometimes a simple array rebuild at O(n) is better if updates are infrequent but queries are many.
- Not handling negative indices or zero: The
i & -itrick works for positive integers. Zero has no LSB, so loops checkingwhile i > 0correctly terminate, but passing 0 to update() will cause an infinite loop since 0 + LSB(0) = 0. Guard against this.
Fenwick Tree vs. Segment Tree: When to Choose Which
Both structures support range queries and updates in O(log n), but they have different strengths. Use this guide to choose:
- Choose Fenwick Tree when: You only need prefix sums, range sums, or other invertible operations (like XOR, multiplication with modular inverse). Memory is tight. Implementation simplicity matters. You're doing point updates with range queries, or range updates with point queries (difference array variant).
- Choose Segment Tree when: You need non-invertible operations like range minimum, maximum, GCD, or matrix multiplication. You need lazy propagation for range updates combined with range queries. You need to maintain more complex aggregate data per node.
In practice, many competitive programmers reach for a Fenwick Tree first because it's faster to code and less error-prone, falling back to a segment tree only when the operation requirements demand it.
Conclusion
The Fenwick Tree stands as one of the most elegant data structures in computer science โ a perfect marriage of bit manipulation and algorithmic design that delivers O(log n) performance for prefix sum queries and point updates using minimal code and memory. Its core insight โ that binary representation can naturally organize partial sums into a traversable tree โ is both beautiful and practical.
You now have a complete understanding of how Fenwick Trees work internally, how to implement them correctly in Python and C++, how to analyze their time complexity rigorously, and how to apply them to real problems like inversion counting, order statistics, and dynamic range queries. The best practices and pitfalls covered here will help you avoid common mistakes and deploy BITs confidently in your own projects. Whether you're optimizing a database aggregation pipeline, solving competitive programming challenges, or building real-time analytics, the Fenwick Tree deserves a prominent place in your data structure toolkit.