QUICK START:HTMLCSSJavaScriptTypeScriptPythonSQLReactNext.jsNode.jsLinux & UbuntuKotlinSwiftC# / .NETJavaGoRustC++DSASystem DesignDevOpsCybersecurityAI / ML
Advanced 26 min readModule: Module 13: Succinct & Compressed Data Structures: Roaring Bitmaps

Succinct Data Structures: Roaring Bitmaps & Rank/Select

Represent massive datasets in compressed memory with Succinct Data Structures: Roaring Bitmaps (Array Containers, Bitmap Containers, Run-Length Encoded RLE Containers), Rank and Select operations on bit vectors, and Wavelet Trees.

What You Will Learn in This Lesson

  • The concept of Succinct Data Structures: operating directly on compressed data without decompressing
  • The 3 internal containers of Roaring Bitmaps: Array (sparse), Bitset (dense), and Run (contiguous ranges)
  • How Lucene, Elasticsearch, Spark, and Redis use Roaring Bitmaps for high-speed set intersections (AND/OR)
  • Rank and Select primitive queries in constant O(1) time

Introduction & Core Concept

Storing sets containing millions of integers (such as user IDs, document IDs in search engines, or analytics tags) requires hundreds of megabytes of RAM if using standard HashSets. A standard bitset consumes fixed memory regardless of density. Roaring Bitmaps partition 32-bit integers by their top 16 bits and dynamically choose between sparse 16-bit integer arrays, uncompressed bitsets, or Run-Length Encoding (RLE) to achieve up to 100x compression with sub-microsecond set intersection speeds.
WHY DOES THIS MATTER IN THE REAL WORLD?

Search engines (Elasticsearch, Apache Lucene) and distributed analytical databases (ClickHouse, Apache Spark, Druid) use Roaring Bitmaps to intersect billion-record filter queries in milliseconds.

Syntax & Structure

javascript
// Roaring Bitmap Container Selection
if (count < 4096) ArrayContainer(uint16[])
else BitsetContainer(uint64[1024])

Simulating Roaring Bitmap Dynamic Container Selection and Set Intersection

javascript
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Roaring Bitmap Dynamic Container Architecture Simulation
class SimpleRoaringBitmap {
constructor() {
// Map high 16-bit chunk -> Container
this.chunks = new Map();
}
add(val) {
const chunkKey = (val >>> 16) & 0xFFFF;
const lowVal = val & 0xFFFF;
if (!this.chunks.has(chunkKey)) {
// Start with sparse Array Container
this.chunks.set(chunkKey, { type: 'ARRAY', data: [] });
}
const container = this.chunks.get(chunkKey);
if (container.type === 'ARRAY') {
if (!container.data.includes(lowVal)) {
container.data.push(lowVal);
container.data.sort((a, b) => a - b);
}
// If cardinality exceeds 4,096 elements, convert to Bitset Container!
if (container.data.length > 4096) {
const bitset = new Uint32Array(2048); // 65,536 bits
container.data.forEach(v => bitset[v >>> 5] |= (1 << (v & 31)));
container.type = 'BITSET';
container.data = bitset;
}
} else if (container.type === 'BITSET') {
container.data[lowVal >>> 5] |= (1 << (lowVal & 31));
}
}
has(val) {
const chunkKey = (val >>> 16) & 0xFFFF;
const lowVal = val & 0xFFFF;
const container = this.chunks.get(chunkKey);
if (!container) return false;
if (container.type === 'ARRAY') {
return container.data.includes(lowVal);
} else if (container.type === 'BITSET') {
return (container.data[lowVal >>> 5] & (1 << (lowVal & 31))) !== 0;
}
return false;
}
}
const rb = new SimpleRoaringBitmap();
rb.add(105);
rb.add(65536 + 10); // Spans into chunk #1
console.log("=== Roaring Bitmap Hybrid Container Engine ===");
console.log("Contains 105:", rb.has(105));
console.log("Contains 65546:", rb.has(65536 + 10));
console.log("Contains 999:", rb.has(999));
console.log("✅ Adaptive memory compression active across chunk boundaries!");

Line-by-Line Technical Breakdown

1Rank and Select Queries: Rank(i) returns the number of 1-bits before index i in O(1) time using pre-computed block sum lookup tables. Select(j) returns the index of the j-th 1-bit. These two operations form the foundation of compressed full-text search indexes (FM-Index).

Try It Yourself (Interactive Editor)

Modify the code in real-time and click Run to test live browser output and console logs.

Intelligent Code Runner & Live Sandbox[JAVASCRIPT]
JAVASCRIPT SOURCE EDITOR
Interactive Live Code

Common Mistakes & How to Avoid Them

#1: Using standard uncompressed `HashSet<Long>` in Java/C# for massive user tracking, exhausting gigabytes of heap RAM.

Standard HashSets box primitives into heap objects with 32 bytes of object header overhead. Roaring Bitmaps compress integers down to fractions of a byte.

Incorrect / Antipattern
Set<Long> userIds = new HashSet<>(); // 32 bytes of overhead per number!
Correct / Professional Solution
RoaringBitmap userIds = new RoaringBitmap(); // ~0.5 to 2 bits per number

Industry Best Practices & Professional Standards

  • Use Roaring Bitmaps for high-cardinality search filtering and tag intersection.
  • Use SIMD-accelerated bitwise instructions (AVX2/AVX-512) for parallel bitmap AND/OR intersections.
  • Store inverted index posting lists as Roaring Bitmaps for instant search filtering.

Lesson Summary & Core Takeaways

  • Roaring Bitmaps adaptively switch between Array, Bitset, and RLE containers.
  • Enables sub-microsecond set intersection with up to 100x memory compression.
  • Widely adopted by Elasticsearch, Lucene, Spark, and high-performance databases.