Fenwick Tree: Every Rank From One Array
A Fenwick tree, also called a Binary Indexed Tree, is a compact data structure for maintaining an array while answering prefix-sum queries efficiently. A useful way to understand it is through a game leaderboard.
Imagine that leaderboard positions are numbered from through . At each position, an array stores a value such as the number of players at that rank, a score contribution, or another quantity that can be added together. Two operations matter:
- Change the value at one position.
- Ask for the total from the first position through a chosen position.
A plain scan can answer a prefix query, but it may need to inspect many positions. A traditional prefix-sum array makes queries fast, but changing one value can require many stored prefixes to be repaired. A Fenwick tree balances these two requirements. It stores carefully selected partial sums so that both a point update and a prefix query follow short paths through an array.
The central rules are:
- Every Fenwick node represents a contiguous range.
- The range length is determined by the node index's lowest set bit, called
lowbit. - A prefix query repeatedly subtracts
lowbitand moves toward the beginning. - A point update repeatedly adds
lowbitand moves toward larger ranges.
The same quantity controls both directions. A query walks downward by subtracting it, while an update walks upward by adding it.
The leaderboard model
Consider a one-dimensional array of values:
position: 1 2 3 4 5 6 7 8
value: a b c d e f g h
Write the value at position as . A prefix sum ending at position is
For example, a query ending at position asks for
A direct scan takes time. If many queries arrive, repeatedly scanning the same prefix wastes work. A traditional prefix-sum array stores every , making a query . However, if changes, every prefix beginning at position or later changes too. Repairing all of those entries can take time.
The Fenwick tree stores neither every individual value in every node nor every complete prefix. Instead, it stores a collection of overlapping blocks. A prefix can be assembled from a few blocks that fit together without overlap. The binary representation of an index determines which blocks are used.
The lowbit rule
For a positive index , lowbit(i) is the value of the lowest set bit in the binary representation of .
Equivalently, using bitwise operations,
This expression extracts the power of two represented by the rightmost binary . Some examples are:
| Index | Binary form | |
|---|---|---|
0001 | ||
0010 | ||
0011 | ||
0100 | ||
0101 | ||
0110 | ||
0111 | ||
1000 |
The important interpretation is not only the bit value. In a Fenwick tree, the node at index stores a range whose length is exactly .
Let the Fenwick array be called tree. Its fundamental invariant is
In words, the node at ends at and extends backward by its lowbit length.
For example:
tree[1]covers position because its length is .tree[2]covers positions through because its length is .tree[3]covers position because its length is .tree[4]covers positions through because its length is .tree[6]covers positions through because its length is .tree[8]covers positions through because its length is .
For an array of length , the coverage is:
| Node | Lowbit | Covered range |
|---|---|---|
The ranges overlap, and that is intentional. A prefix query does not add every overlapping range. It selects a sequence of ranges that fit together exactly.
Why the ranges end at the node index
The right endpoint of tree[i] is always . The left endpoint is
The lowbit tells us how large a power-of-two-aligned block ends at . For , the binary form is 0110, so the lowbit is . The node covers two positions ending at :
Therefore, tree[6] stores .
For , the binary form is 1100, and the lowbit is . The node covers four positions:
So tree[12] stores the sum over positions through .
This arrangement gives the implicit tree its shape. Odd indices have lowbit and store one-element blocks. Indices divisible by can store larger blocks, and powers of two store especially large prefixes. The structure is represented by an array rather than pointer-linked nodes, but the ranges still form a hierarchy of increasingly large aggregates.
Prefix queries: jumping downward by lowbit
Suppose the request is the sum from position through position . Start with index = r and answer = 0.
At every step:
- Add
tree[index]to the answer. - Replace
indexwithindex - lowbit(index). - Stop when the index becomes zero.
The index transition is
Why does this work? The current node covers the final block of the still-unprocessed prefix. Once that block is added, subtracting its length moves to the position immediately before that block.
Example: prefix through position
For , the index sequence is
The visited nodes are , , and . Their ranges are:
- Node covers .
- Node covers .
- Node covers .
Together, they partition the requested prefix:
Therefore,
No position is counted twice, and no position is omitted.
The pseudocode is:
prefix_sum(index):
answer = 0
while index > 0:
answer += tree[index]
index -= lowbit(index)
return answer
The loop does not move one position at a time. It removes one binary component at each step. That is why the number of iterations is logarithmic in the array length.
Example with numeric values
Suppose the values are:
position: 1 2 3 4 5 6 7
value: 4 2 7 1 3 5 6
The relevant nodes for a prefix through are:
The query returns
The algorithm reaches the answer by combining stored ranges instead of scanning the seven original values.
Point updates: climbing upward by lowbit
Now consider changing one array position. Suppose the value at position changes by an amount called .
If the old value is replaced by a new value, calculate
Every Fenwick node whose range contains must increase by . Begin at index = p. Add the change to tree[index], then move to the next larger node with
Continue while the index is within the Fenwick array.
The pseudocode is:
add(index, delta):
while index <= n:
tree[index] += delta
index += lowbit(index)
This operation receives a delta, not necessarily an entirely new value. If the value at position changes from to , the delta is . Adding to every covering node would be incorrect because those nodes already include the old value.
Example: update position
For an array with at least positions, begin at .
If , the visited nodes are , , and . Their ranges are:
- Node covers .
- Node covers .
- Node covers .
Each range contains position , so each stored sum receives the same delta. The next index, , is outside the array and terminates the update.
Why adding lowbit finds the covering nodes
A Fenwick node is defined by the interval ending at its own index. Starting at position , the first node is the smallest stored block ending at . Adding the lowbit moves to a larger block that still contains .
For position , the path is . The ranges grow from a single element to two elements and then to the complete eight-element prefix. This is the Fenwick version of climbing from a local contribution to larger aggregate ranges.
The query and update paths are reverse in spirit:
- A query uses a node, then moves left by subtracting its range length.
- An update changes a position, then moves right to larger covering nodes by adding their range length.
Neither operation needs explicit pointers. The array index and bit arithmetic determine the path.
A complete small example
Take the values:
position: 1 2 3 4 5 6 7 8
value: 2 1 4 3 6 2 5 1
The Fenwick entries are built from their defined ranges:
Now ask for the prefix through position . The query path is .
Node contributes positions and , while node contributes positions through :
That matches the original values:
Next, suppose position increases from to . The delta is
The update path is:
Therefore, add to nodes , , and . The affected entries become:
node 3: 4 -> 9
node 4: 10 -> 15
node 8: 24 -> 29
Nodes , , and all cover position . Other nodes do not need modification because their ranges do not contain the changed position.
The prefix through position can now be answered with the same query path:
The original prefix was , and increasing one included value by makes the new prefix .
The core invariant
The most important correctness statement is the Fenwick invariant:
Every operation should preserve this statement.
The invariant after a point update
Suppose increases by . A node needs to change exactly when its interval contains :
For every such node, its stored range sum increases by . The update path visits those relevant nodes in increasing order of their coverage. Thus, after adding to every visited node, the invariant remains true.
Nodes whose intervals do not contain are left untouched, as required.
The invariant during a prefix query
At the beginning of each query iteration, the unprocessed part of the requested prefix ends at the current index. The node at that index covers the final block of that unprocessed part. Adding the node's value accounts for that block. Subtracting its lowbit moves the boundary immediately before the block.
Eventually, the index becomes zero. The visited ranges are disjoint and together cover exactly the original prefix, so their stored sums add to the requested result.
The query is correct because it partitions the prefix; it does not require every node to represent a complete prefix.
Building the Fenwick tree
A Fenwick tree can be initialized by applying add for every array position. If there are positions, this repeated-update construction takes time.
The direct construction idea is:
for index from 1 through n:
add(index, values[index])
After construction, every node must satisfy the range-sum invariant. Repeated point updates are often the easiest method to understand and trace by hand because they use the same update operation needed later.
The internal Fenwick array is normally one-based. If application data uses zero-based positions, convert an application position to an internal index before performing a query or update. The conversion must be consistent in every operation.
Range sums from two prefixes
The basic Fenwick operation is a prefix sum, but an arbitrary contiguous range can be obtained from two prefixes. For positions through ,
For example, the sum over positions through is
The prefix through removes exactly the values before the requested interval. Since each prefix query takes time, the range query also takes time.
This formula is especially useful in the leaderboard model. A query can ask for all values through one rank, or it can ask for the total associated with a range of ranks. Both are supported by the same prefix routine.
Why one-based indexing matters
The lowbit operation is normally applied to positive indices. Index zero is special: its lowbit is zero, so a loop that repeatedly adds or subtracts lowbit at index zero would not make progress.
For that reason, a common layout is:
application position: 0 1 2 3
Fenwick index: 1 2 3 4
The exact application-to-internal mapping can differ, but the internal Fenwick indices should normally run from through . When translating an application index, apply the same convention to:
- The original value assignment.
- The point update.
- The prefix-query endpoint.
- The endpoint in a range query.
An off-by-one error can produce plausible results for some positions while corrupting the structure for others. Indexing is therefore part of the data structure's invariant, not merely an implementation detail.
Common mistakes
Treating a node as a complete prefix
tree[i] is generally not the sum from position through position . It is the sum over the block ending at whose length is lowbit(i).
For example, tree[6] covers positions and 166$ is assembled as tree[6] + tree[4].
Walking one position at a time
A prefix query should not decrement its index by one. The correct transition is
Likewise, an update should not increment its index by one. It must use
These jumps are what make the operations efficient and what match the stored ranges.
Adding a replacement value instead of a delta
If a value changes from to , the amount to propagate is
Propagating would double-count the old contribution already present in the affected nodes.
Forgetting the stopping condition
A query stops when its index reaches zero. An update stops when its index exceeds . These conditions are essential for termination.
Mixing zero-based and one-based positions
If the application uses zero-based positions but the Fenwick array uses one-based indices, convert at the boundary. Do not sometimes add one and sometimes omit it. The path generated by lowbit is meaningful only for the internal index used to store the data.
Tracing the structure by hand
When learning or debugging, write down four columns:
| Current index | Binary form | Lowbit | Next index |
|---|---|---|---|
0111 | |||
0110 | |||
0100 |
For a query, the next index is current minus lowbit. For an update, it is current plus lowbit. Then list the range represented by each visited node.
For a query through :
query index 7: use [7,7]
query index 6: use [5,6]
query index 4: use [1,4]
For an update at :
update node 5: change [5,5]
update node 6: change [5,6]
update node 8: change [1,8]
This method makes it easy to check two different properties. First, the ranges used by a query should be disjoint and should cover the requested prefix. Second, every node visited by an update should contain the changed position.
A small array of eight positions is particularly useful because the binary forms remain easy to inspect. For a larger index, the same arithmetic applies; only the number of binary digits increases.
Fenwick tree as binary decomposition
The structure can be viewed as a binary decomposition of a prefix. The binary representation of the endpoint determines which block sizes are needed.
For example, has binary form 0111. Its query path removes the lowest set bit at each step:
The corresponding block lengths are , , and . They add to :
For endpoint , the path is . The block lengths are and $4:
This is why the selected intervals fit perfectly. The query extracts binary-sized suffix blocks from the current prefix, with the lowbit identifying the smallest block removed at each stage.
Updates use the complementary view. Starting at a position, adding lowbit moves into progressively larger ranges that include that position. Binary arithmetic identifies the next aggregate that needs the changed contribution.
This implicit binary structure explains why the data structure is called a tree even though its implementation is an array. The parent-like relationships are encoded by index transitions rather than explicit references.
Leaderboard interpretation
Return to the leaderboard. Suppose each position represents a rank and the array contains a quantity associated with that rank. A prefix query through rank asks for the accumulated quantity from the beginning of the leaderboard through rank .
If one rank's value changes, the Fenwick tree does not recalculate every prefix from that rank onward. It changes only the stored blocks that cover that rank. Later prefix queries reconstruct the correct total by combining those updated blocks.
For a query endpoint such as rank , the tree might combine:
- The block for rank $7.
- The block for ranks through .
- The block for ranks through .
The original array remains the base sequence, while the Fenwick array provides a structured set of partial sums from which requested prefixes can be assembled. The structure is useful whenever values change at individual positions and the important aggregate is an additive prefix or interval total.
The tree does not require a pointer-linked representation. The array index and its lowbit determine a node's interval, its query predecessor, and the larger nodes affected by an update.
Complexity
For an array of length , a prefix query repeatedly removes a lowbit from its index. A point update repeatedly moves to a larger index by adding a lowbit. Both paths have logarithmic length in the standard Fenwick-tree setting.
The usual bounds are:
- Point update: .
- Prefix-sum query: .
- Range sum using two prefix sums: .
- Storage: .
- Repeated-update construction: .
The important trade-off is clear. The structure uses linear storage and does not make either operation constant time, but it supports both changing one value and asking for an aggregate prefix in logarithmic time.
A compact implementation plan
A reliable implementation can be designed in the following order.
Step 1: Choose the internal indexing convention
Use indices from through for the Fenwick array. Decide how application positions map to those indices before writing the query and update routines.
Step 2: Implement lowbit
The standard bitwise form is:
lowbit(index) = index & (-index)
The input should be a positive internal index during normal Fenwick traversal.
Step 3: Implement point addition
Start at the changed position. Add the delta to the current node. Move upward by adding lowbit until the index exceeds .
Step 4: Implement prefix sum
Start at the requested endpoint. Add the current node's value. Move downward by subtracting lowbit until the index becomes zero.
Step 5: Implement range sum if needed
Return the prefix through the right endpoint minus the prefix before the left endpoint.
Step 6: Test a tiny array by hand
Use a short array such as eight positions. Write each node's interval, trace a query path, and trace an update path. Small binary examples expose indexing and boundary errors quickly.
Final perspective
The Fenwick tree is compact because it stores the partial ranges needed by its two traversals. The lowbit supplies the shape:
- At node , the stored interval has length .
- A prefix query consumes that interval and moves to .
- A point update propagates the change to larger covering nodes using .
The key invariant is
Once that invariant is clear, the algorithms become natural. A query partitions a prefix into disjoint stored blocks. An update changes every stored block that contains the modified position. Both operations follow binary paths of logarithmic length while using only an array.
When working with a leaderboard or any changing sequence of additive values, keep this checklist nearby:
- Use positive, consistent internal indices.
- Interpret each node as a lowbit-sized range ending at that node.
- Query by subtracting lowbit.
- Update by adding lowbit.
- Propagate a delta for a changed value.
- Use two prefixes to obtain an arbitrary range sum.
- Verify the invariant on a small example before scaling up.
That combination of an explicit range invariant and implicit binary navigation is what makes the Fenwick tree an efficient tool for dynamic prefix sums.