โ† Back to DevBytes

Fenwick Trees: Implementation and Time Complexity Analysis

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

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:

  1. Update a single element's value
  2. 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

Advantages Over Segment Trees

While segment trees can handle a broader class of operations (range minimum, maximum, GCD, etc.), Fenwick Trees offer distinct advantages:

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:

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

Common Pitfalls

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:

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.

๐Ÿš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started โ€” $23.99/mo
โ† Back to all articles