<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rosario De Chiara</title>
    <description>The latest articles on DEV Community by Rosario De Chiara (@rosdec).</description>
    <link>https://dev.to/rosdec</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F875714%2F73d73dc7-97bc-4267-a7bc-13c4f5d493cc.jpeg</url>
      <title>DEV Community: Rosario De Chiara</title>
      <link>https://dev.to/rosdec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rosdec"/>
    <language>en</language>
    <item>
      <title>Smart multifacet filters in JS using Bitmaps</title>
      <dc:creator>Rosario De Chiara</dc:creator>
      <pubDate>Tue, 26 May 2026 16:54:06 +0000</pubDate>
      <link>https://dev.to/rosdec/smart-multifacet-filters-in-js-using-bitmaps-3d01</link>
      <guid>https://dev.to/rosdec/smart-multifacet-filters-in-js-using-bitmaps-3d01</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Modern frontend applications are starting to look less like simple view layers and more like small databases running directly in the browser.&lt;/p&gt;

&lt;p&gt;Think about an e-commerce catalog, an analytics dashboard, or an admin panel. In all of these cases, the UI isn’t just rendering data—it’s constantly slicing it, recombining it, and reacting to user input in real time. Filtering becomes one of the most common operations, and the most natural way to express it in JavaScript is still the familiar chain of &lt;code&gt;Array.filter()&lt;/code&gt; calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const filtered = products  
 .filter(p =&amp;gt; p.category === selectedCategory)  
 .filter(p =&amp;gt; p.inStock)  
 .filter(p =&amp;gt; p.price &amp;lt; maxPrice);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads well and does exactly what you expect: take a list, narrow it down step by step. For small datasets, there’s nothing wrong with this approach. The problem starts when the data grows or when filtering becomes part of a tight interaction loop—every checkbox toggle, every slider movement, every keystroke.&lt;/p&gt;

&lt;p&gt;The point is that filters are rarely applied once: they are run over and over again, often across tens or hundreds of thousands of items, and each step in that chain quietly walks the entire array again.&lt;/p&gt;

&lt;p&gt;This is not a new problem on the backend: databases ran into it decades ago and solved it with a different mental model by replacing scanning records with precomputed indexes, turning the filtering into fast set operations. One of the most effective techniques they use is bitmap indexing, where filters become simple bitwise combinations rather than repeated object traversal.&lt;/p&gt;

&lt;p&gt;In this article, we’ll bring that idea into JavaScript and see what happens when you treat your frontend data layer a bit more like a database engine. You can find the fully working code on the &lt;a href="https://github.com/rosdec/bitmap_filters" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. The interactive example on the repository contains a time comparison of traditional array filtering and the techniques described here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea behind bitmap indexes
&lt;/h2&gt;

&lt;p&gt;Bitmap indexes work by precomputing binary representations for attributes.&lt;/p&gt;

&lt;p&gt;Imagine this dataset:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Red&lt;/th&gt;
&lt;th&gt;Blue&lt;/th&gt;
&lt;th&gt;Size M&lt;/th&gt;
&lt;th&gt;In Stock&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;P1 is Red, Size M and in stock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;P2 is Blue, Size M and is not in stock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;P3 is Red, and is in stock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;P4 is Red, Size M and is not in stock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each attribute becomes a bitmap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;red      = 1000  
blue     = 0100  
sizeM    = 0010  
inStock  = 0001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering becomes a bitwise operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;red AND sizeM AND inStock

1000  
0010  
0001  
----  
1011
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the first product matches all filters.&lt;/p&gt;

&lt;p&gt;Instead of repeatedly scanning arrays, we combine compact binary structures using highly optimized CPU operations.&lt;/p&gt;

&lt;p&gt;This is one of the reasons bitmap indexes are widely used in analytical databases and search engines.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example of use
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Generating a realistic dataset
&lt;/h2&gt;

&lt;p&gt;The demo starts by constructing a synthetic—but stable—product catalog. Instead of hardcoding data or relying on an API, the implementation uses a seeded pseudo-random generator to ensure reproducibility across page loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let _seed = 42;

function rng() {  
 _seed = (_seed * 9301 + 49297) % 233280;  
 return _seed / 233280;  
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This small detail matters more than it seems. Because the dataset is deterministic, performance comparisons between runs are consistent—something you don’t get with Math.random(). &lt;/p&gt;

&lt;p&gt;The dataset itself is built via &lt;code&gt;Array.from&lt;/code&gt;, producing &lt;code&gt;10,000&lt;/code&gt; products (you can vary this by modifying the &lt;code&gt;PRODUCT_COUNT&lt;/code&gt; constant) with attributes like color, size, inStock, and price:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const products = Array.from({ length: PRODUCT\_COUNT }, (\_, i) \=\&amp;gt; ({  
 id:      i,  
 name:    `${pick(ADJS)} ${pick(NOUNS)}`,  
 color:   pick(COLORS),  
 size:    pick(SIZES),  
 inStock: rng() &amp;gt; 0.38,  
 price:   Math.floor(rng() * 90) + 9,  
}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each product is intentionally simple and categorical. That’s not a limitation—it’s exactly what makes this dataset ideal for bitmap indexing. Faceted search systems (like e-commerce filters) are dominated by discrete attributes, and this structure mirrors that reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  From &lt;code&gt;Array.filter&lt;/code&gt; to bitmap filtering
&lt;/h2&gt;

&lt;p&gt;At a glance, the traditional approach uses chained &lt;code&gt;Array.filter&lt;/code&gt; calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let arr = products;  
if (colors.length &amp;gt; 0) arr = arr.filter(p =&amp;gt; colors.includes(p.color));  
if (sizes.length &amp;gt; 0) arr = arr.filter(p =&amp;gt; sizes.includes(p.size));  
if (inStockOnly) arr = arr.filter(p =&amp;gt; p.inStock); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intuitive and expressive: each filter step narrows down the dataset. But under the hood, it repeatedly scans the array—O(n) per filter—leading to cumulative cost as filters stack.&lt;/p&gt;

&lt;p&gt;The bitmap approach flips the perspective entirely. Instead of filtering objects, it filters indices using precomputed bitsets.&lt;/p&gt;

&lt;p&gt;First, we build indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const idx = {  
 color: Object.fromEntries(COLORS.map(c =&amp;gt; [c, new FastBitSet()])),  
 size: Object.fromEntries(SIZES.map(s =&amp;gt; [s, new FastBitSet()])),  
 inStock: new FastBitSet(),  
};

products.forEach((p, i) =&amp;gt; {  
 idx.color[p.color].add(i);  
 idx.size[p.size].add(i);  
 if (p.inStock) idx.inStock.add(i);  
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each bitset represents a subset of product indices. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;idx.color.red&lt;/code&gt; → all indices of red products&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;idx.size.M&lt;/code&gt; → all medium-sized products&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;idx.inStock&lt;/code&gt; → all available products&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This transforms filtering into set algebra.&lt;/p&gt;

&lt;p&gt;The core logic starts from a “universe” of all indices and then refines it by applying intersection and union operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let result = universe.clone();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then applies constraints using bitwise operations. In the following code, we map the logical “or” operator by using the .union operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const colorSet = new FastBitSet();  
colors.forEach(c =&amp;gt; colorSet.union(idx.color[c]));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This corresponds to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;color = red OR blue OR green
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same way multiple selections within the same facet are merged via union, we map the logical “and” operator by using the .intersection operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;result.intersection(colorSet);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This corresponds to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(color condition) AND (size condition) AND (stock condition)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each additional filter reduces the result set via intersection.&lt;/p&gt;

&lt;p&gt;Putting it together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (sizes.length \&amp;gt; 0\) {  
 const sizeSet = new FastBitSet();  
 sizes.forEach(s =&amp;gt; sizeSet.union(idx.size[s]));  
 result.intersection(sizeSet);  
}

if (inStockOnly) {  
 result.intersection(idx.inStock);  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually, this translates the array pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;products  
 .filter(color condition)  
 .filter(size condition)  
 .filter(stock condition)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;universe  
 ∩ (color.red ∪ color.blue)  
 ∩ (size.M ∪ size.L)  
 ∩ inStock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key shift is that instead of iterating over objects repeatedly (this happens with the filter function on arrays), we manipulate compact bitmaps where each operation is vectorized and CPU-efficient.&lt;/p&gt;

&lt;p&gt;Finally, the matching indices are materialized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const matchedIds = result.array();

and mapped back to actual product objects only at the end:

matchedIds.map(i =&amp;gt; products[i])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This last operation maps the remaining &lt;code&gt;matchedIds&lt;/code&gt; to the products to update the UI, and that is another subtle but important optimization by pushing back this operation just on the final set of matched identifiers. &lt;/p&gt;

&lt;h1&gt;
  
  
  Production considerations
&lt;/h1&gt;

&lt;p&gt;In production, bitmap-based filtering is typically implemented as an index-first strategy: instead of repeatedly scanning objects, you precompute compact index structures and combine them efficiently at query time. On the server, this pattern is often handled by specialized libraries or search engines—using structures like compressed bitmaps (e.g., &lt;a href="https://roaringbitmap.org/" rel="noopener noreferrer"&gt;RoaringBitmap&lt;/a&gt;) that allow fast unions and intersections at scale, even across millions of records. On the client, the same idea can be applied to moderate in-memory datasets to deliver instant, zero-latency filtering for common facets, while delegating heavier or high-cardinality queries back to the backend. The main tradeoffs to manage are index maintenance (keeping bitmaps in sync with data updates) and memory usage, but when filters are frequent and latency matters, this approach provides a significant and predictable performance advantage.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Bitmap indexes are a good example of how ideas traditionally associated with database engines are increasingly relevant in frontend engineering.&lt;/p&gt;

&lt;p&gt;Instead of repeatedly scanning arrays of objects, we can transform filtering operations into compact bitwise computations that scale much more efficiently as datasets grow.&lt;/p&gt;

&lt;p&gt;For small applications, standard filtering techniques are usually simpler and perfectly sufficient. But for large client-side datasets, analytics dashboards, faceted search interfaces, and local-first applications, bitmap indexes can dramatically improve responsiveness while keeping implementation complexity relatively manageable.&lt;/p&gt;

&lt;p&gt;Modern frontend applications increasingly resemble miniature data systems running directly in the browser. Techniques like bitmap indexing show that many optimizations once reserved for databases can now directly improve frontend user experiences as well.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>ui</category>
      <category>algorithms</category>
      <category>ux</category>
    </item>
  </channel>
</rss>
