Articles

What Is a Bitmap Index?

A bitmap index uses a bit array per distinct value to speed up queries on low-cardinality columns, and combines multiple filters with fast bitwise AND/OR.

The Lycoris Team The Lycoris Team · · 4 min read
An abstract illustration of database tables

A bitmap index is a database index that represents each distinct value in a column as a bit array — one bit per row, set to 1 if that row holds the value and 0 otherwise. Instead of storing pointers to matching rows the way a B-tree does, a bitmap index stores presence as bits, which makes combining several filter conditions extremely fast: the database just runs bitwise AND, OR, or NOT across the arrays.

How it’s structured

Picture a status column on a million-row orders table with only three possible values: pending, shipped, and delivered. A bitmap index on that column builds one bit array per value, each a million bits long:

row:        1  2  3  4  5  6  ...
pending:    1  0  0  1  0  0
shipped:    0  1  0  0  0  1
delivered:  0  0  1  0  1  0

A query for WHERE status = 'shipped' just reads the shipped bit array and returns the rows where the bit is 1 — no tree traversal, no pointer chasing. A query for WHERE status = 'shipped' AND region = 'west' — assuming a bitmap index also exists on region — computes the bitwise AND of the two bit arrays and gets the matching rows directly from the resulting bitmap, with no need to intersect row-ID lists the way a B-tree-based query plan typically would.

This is the property that makes bitmap indexes distinct from every other common index type: they’re built for combining multiple filter conditions cheaply, not for optimizing any single one.

Why cardinality matters so much

Bitmap indexes are extremely efficient when a column has a small number of distinct values relative to the row count — low cardinality. A status column with 3–10 possible values, a boolean flag, or a region column with a couple dozen options are ideal candidates: the bit arrays stay compact and dense, and bitwise operations over them are cheap.

The same design falls apart for high-cardinality columns. An index on email or order_id, where nearly every row has a distinct value, would need one bit array per distinct value — millions of nearly-empty arrays, each a million bits long with a single 1 in it. That’s enormously wasteful compared to a B-tree, which handles high-cardinality, highly selective lookups efficiently by design. This is the opposite of how B-tree indexes behave: B-trees get less useful as cardinality drops toward a handful of values, since the database ends up scanning a large fraction of the table anyway once a value matches millions of rows.

Bitmap indexB-tree index
Best forLow-cardinality columns (few distinct values)High-cardinality columns (many distinct values)
Combining filtersFast bitwise AND/OR across columnsTypically one index used per query, or index intersection
Storage as cardinality risesGrows poorly — one bitmap per valueScales well
Concurrent write behaviorPoor — a single row update can touch a whole bitmapGood — updates are localized
Common inData warehouses, read-heavy analyticsOLTP systems, general-purpose querying

Why writes are the trade-off

The same structure that makes bitmap indexes fast for read-heavy filtering makes them expensive to maintain under write-heavy workloads. Updating a single row’s indexed column means flipping bits in more than one bitmap — clearing the old value’s bit, setting the new value’s bit — and depending on the implementation, that can require locking a wider chunk of the bitmap than a B-tree would ever need to lock for the equivalent row update. This is why bitmap indexes are common in analytical, OLAP-style systems and data warehouses, where data is loaded in batches and queried heavily afterward, and rare in OLTP systems with constant row-by-row inserts and updates.

Where you’ll actually encounter them

Not every database offers bitmap indexes as a first-class, persisted index type. Oracle has supported them natively for decades and they’re a standard recommendation there for low-cardinality warehouse columns. PostgreSQL doesn’t let you create a bitmap index directly — instead, its query planner can build a bitmap heap scan on the fly at query time, combining several regular indexes (typically B-trees) into a temporary in-memory bitmap when a query filters on multiple columns, which gets much of the same benefit without a dedicated on-disk structure. Many columnar analytical databases use bitmap-style encoding internally as part of how they store and compress low-cardinality columns in the first place.

The takeaway

A bitmap index trades the general-purpose flexibility of a B-tree for a specific strength: cheap bitwise combination of filters over columns with only a handful of distinct values. That makes it a strong fit for status flags, categories, and other low-cardinality columns in read-heavy analytical workloads, and a poor fit for anything with high cardinality or frequent writes — which is exactly the profile a B-tree handles well instead.

The Lycoris Team The Lycoris Team · · 5 min read

Clustered vs Non-Clustered Index Explained

A clustered index determines the physical row order on disk; a non-clustered index is a separate lookup structure. How they differ and when to use each.

#Databases #SQL #Performance
The Lycoris Team The Lycoris Team · · 4 min read

The N+1 Query Problem: What It Is and How to Fix It

The N+1 query problem fires one query per row instead of one query total, quietly turning a fast page into hundreds of round trips. How to spot and fix it.

#Databases #Performance #SQL
The Lycoris Team The Lycoris Team · · 5 min read

Hash Join vs Nested Loop Join vs Merge Join

Hash joins, nested loop joins, and merge joins are the three ways a database executes a JOIN — here's when the query planner picks each one.

#Databases #SQL #Performance