skip to content

questions

5

Bitmap indexes are conventionally recommended for low-cardinality columns. Explain why that guidance exists, and what actually happens to the index as the number of distinct values grows into the millions.

level: middleimportance: must knowfreq 34%

answer

  1. Size ≈ rows × distinct values before compression
  2. Compressed total tracks set bits, ~O(rows)
  3. Payoff needs many rows per value
  4. High cardinality → sparse vectors + per-value overhead
  5. Criterion is rows-per-value, not absolute distinct count

basics

~20 s

Storage grows with distinct values because each value needs its own bit vector, and the benefit comes from each value matching many rows. At very high cardinality the bitmaps become extremely sparse, per-value overhead dominates, and each value matches so few rows that a B+Tree does the job better.

solid answer

~60 s

Two things drive the guidance. **Size.** Uncompressed the index is *rows × distinct values* bits, so every additional distinct value adds a whole vector. Compression rescues the sparse case — long zero runs collapse — so the total does not truly explode, but per-value metadata and chunk overhead accumulate, and the index stops being dramatically smaller than a B+Tree. **Benefit.** Bitmaps pay off when a predicate matches *many* rows: representing 20% of a table as a compact bit vector and returning positions in physical order is far better than millions of random row fetches from a tree. At million-value cardinality each value matches a handful of rows — exactly the case a B+Tree descent handles optimally — so the advantage disappears. The honest criterion is not "few distinct values" but **many rows per value, with queries that combine several such predicates**. On a ten-billion-row table, a column with 100,000 distinct values still averages 100,000 rows per value and bitmaps remain viable. Cardinality relative to row count is what matters.

go deeper

for a junior

Say that each distinct value needs its own bit vector, so more values means more vectors, and that bitmaps help most when lots of rows share a value.

for a middle

Separate the two arguments — storage and benefit — and note that compression blunts the storage argument while the benefit argument still holds.

for a senior

Reframe the rule as rows-per-value plus query shape, justify why unselective predicates are the bitmap's home turf, and mention ordering loss and batch-load assumptions.

for a principal

Treat the guidance as a heuristic over a cost model: reason from selectivity, combination breadth, load pattern and the availability of columnar alternatives rather than a cardinality number.

## Restating the structure so the reasoning follows A bitmap index stores one bit vector per distinct value of the column, each vector holding one bit per row. Two quantities therefore control everything: **d**, the number of distinct values, and **n**, the number of rows. ## The size argument Uncompressed size is `n × d` bits. For n = 100 million: - d = 4 (a status flag): 50 MB. - d = 1,000 (a product category): 12.5 GB. - d = 10 million (a customer id): 125 TB. The naive extrapolation says bitmap indexes explode with cardinality. Real implementations compress, and this is where the naive story is wrong in an important way. Across all d vectors, exactly n bits are set in total (each row belongs to exactly one value). As d rises, each individual vector becomes overwhelmingly zeros, and run-length or word-aligned compression collapses those zero runs to almost nothing. The compressed total stays roughly O(n) — proportional to the number of set bits, not to `n × d`. So the real high-cardinality costs are subtler: - **Per-value overhead.** Each distinct value needs a lookup entry and at least one physical chunk. With ten million values that is ten million small objects — metadata, fragmentation, and a directory as large as a B+Tree's. - **Poor compression on scattered singletons.** A value appearing in one row still needs a chunk describing where that bit lives; the encoded form is not free. - **Maintenance overhead per changed row** grows because chunks become small and numerous. The end state is an index roughly the size of a B+Tree with none of a B+Tree's advantages. ## The benefit argument, which matters more Sizing is the lesser half. The decisive question is what the access path *does with* the result. A B+Tree index scan fetches rows one locator at a time, in key order, which is effectively random against the table. That is optimal when few rows match: three tree levels then one row fetch. It is terrible when many rows match — retrieving 20% of a large table by random fetches typically costs more than reading the whole table sequentially, which is why planners abandon indexes above a selectivity threshold. A bitmap represents "the 20% of rows that match" in a compact form, combines it with other predicates using bitwise operations before touching any data, and then produces row positions in **ascending physical order**, so table access is sequential and no page is visited twice. That is precisely the regime a B+Tree cannot serve. So the value of a bitmap is highest when **many rows share a value** — low cardinality relative to row count — and vanishes when each value is nearly unique, because then the tree's strength (going straight to one row, in order, supporting ranges and top-N) is what you need. ## The correct criterion "Low cardinality" is a proxy that misleads on very large tables. The criteria that actually predict a good fit: 1. **Rows per value is large** (equivalently, d is small relative to n — a classic rule of thumb is d below roughly 1% of n, though modern engines stretch this considerably). 2. **Queries combine several such predicates**, so bitwise AND across columns produces a small final set from individually unselective inputs. This is where bitmaps are unbeatable and where a single composite B+Tree cannot cover every ad-hoc combination. 3. **The table is loaded in batches and read heavily**, because in-place bitmap maintenance under concurrent writes is the structure's fatal weakness. 4. **Ordered access is not required** from this index, since bitmap output is in physical order. If all four hold, cardinality in the tens or hundreds of thousands can still be perfectly fine on a multi-billion-row table. ## What actually degrades as cardinality rises - Compression ratio per vector improves (more zeros) but per-value fixed cost multiplies, so total size flattens out near a B+Tree's. - Query benefit collapses: each predicate now yields few rows, so combining bitmaps buys nothing over a single tree descent. - Maintenance worsens: more, smaller chunks to rewrite per DML statement. - The value-lookup structure itself becomes large enough that finding the right bitmap is no longer trivially cheap. ## Practical guidance - Judge by *rows per distinct value*, not by an absolute distinct count. - Prefer bitmaps on the columns that appear together in filters, especially dimension keys and flags on a large fact table. - Use a B+Tree where the predicate is selective, where ranges or ordering matter, or where uniqueness must be enforced — a bitmap cannot enforce uniqueness. - Expect the engine's own cost model to decide anyway; a bitmap index that is never selected still costs load time and maintenance, so drop it. ## One-liner "Size grows with distinct values and the payoff grows with rows per value, so the index makes sense exactly when many rows share each value — and 'low cardinality' is shorthand for that ratio, not an absolute count."

  • If compression keeps the total size roughly proportional to the number of rows, why is high cardinality still bad?
    Because the cost stops being about bytes. Each distinct value still needs a lookup entry and at least one physical chunk, so millions of values mean millions of small objects to manage and maintain. More importantly the benefit disappears: a nearly-unique value matches a handful of rows, which is exactly the case a B+Tree descent handles optimally and where bitwise combination buys nothing.
  • Is a bitmap index on a 100,000-distinct-value column ever reasonable?
    Yes, if the table is large enough that each value still covers many rows — on a ten-billion-row fact table that is 100,000 rows per value on average. The criterion is rows per distinct value together with the query pattern, not an absolute cardinality threshold. It also requires the table to be batch-loaded rather than concurrently updated.

saying these in an interview costs you the question

  • Claiming bitmap index size is literally rows × distinct values in practice, ignoring compression
  • Using an absolute distinct-value threshold with no reference to table size
  • Saying bitmap indexes are 'only for boolean or flag columns'
  • Assuming a bitmap index can replace a unique index or enforce uniqueness
  • Recommending bitmap indexes for selective point lookups on a large table

context

open as a page

Why are bitmap indexes considered unsuitable for tables receiving concurrent row-level updates from many transactions, yet perfectly acceptable on a table that is rebuilt or loaded in a nightly batch?

level: seniorimportance: must knowfreq 33%

basics

~20 s

Bitmaps are stored as compressed chunks covering ranges of rows, so changing one row means decompressing, editing and rewriting a whole chunk, and the lock covers every row in it. Two transactions updating unrelated rows in the same chunk block each other, producing serialization and deadlocks.

open as a page

What is a bitmap index, and how does it physically represent the set of rows that match a particular column value?

level: juniorimportance: should knowfreq 35%

basics

~20 s

A bitmap index stores one bit vector per distinct column value, with one bit per row: bit set means that row has that value. Answering a predicate means fetching one bitmap and turning its set bits back into row locations, and combining predicates is bitwise AND or OR.

open as a page

A query filters a very large table on five different columns at once, none of which is selective on its own. Explain how a bitmap-based access path combines those predicates, and why that can beat choosing the single best index and filtering the rest.

level: middleimportance: should knowfreq 28%

basics

~20 s

The engine gets a bitmap of matching rows for each predicate, then bitwise ANDs them — 64 rows per machine word — producing the final row set before touching the table. Only surviving rows are fetched, and in physical order, instead of fetching everything one predicate matched.

open as a page

A query plan on a table that has only ordinary B+Tree indexes shows a bitmap being built from an index scan and then used to read the table. What is the engine doing there, why does it bother building a bitmap, and what does it cost?

level: seniorimportance: should knowfreq 32%

basics

~20 s

It scans the index and collects matching row locations into an in-memory bitmap instead of fetching each row immediately. That lets it visit table pages once each in physical order instead of randomly, and lets it combine several indexes with bitwise AND or OR before touching data.

open as a page