<?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: Federico Kereki</title>
    <description>The latest articles on DEV Community by Federico Kereki (@fkereki).</description>
    <link>https://dev.to/fkereki</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F179209%2F79746179-da0b-4f0c-a8ce-eb173a00a028.jpeg</url>
      <title>DEV Community: Federico Kereki</title>
      <link>https://dev.to/fkereki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fkereki"/>
    <language>en</language>
    <item>
      <title>Quotient Filters - Creating and Searching</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Fri, 16 Oct 2026 11:39:29 +0000</pubDate>
      <link>https://dev.to/fkereki/part-7-quotient-filters-creating-and-searching-3of8</link>
      <guid>https://dev.to/fkereki/part-7-quotient-filters-creating-and-searching-3of8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the &lt;a href="https://dev.to/fkereki/part-6-quotient-filters-the-theory-228a"&gt;previous article of this series&lt;/a&gt;, we saw how a Quotient Filter is used. Here we'll see how to size and search those filters, with actual&amp;nbsp;code.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Creating a Quotient&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;Before we look at the code for creating a QF, let's work out what its parameters should be. Suppose we want to add &lt;em&gt;n&lt;/em&gt; values to a QF with an α false-positive rate below ε and a load factor below α. (Why the latter? A QF is similar to a hash table in some ways, and the higher the load factor, the slower all operations are. We might want α below 0.75, for example.) How should we size the QF?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need &lt;em&gt;n&lt;/em&gt;/&lt;em&gt;s&lt;/em&gt; &amp;lt; α and &lt;em&gt;s&lt;/em&gt; = 2^&lt;em&gt;q&lt;/em&gt; because we want the table length to be a power of 2. This leads to &lt;em&gt;q&lt;/em&gt; = ⌈log₂(&lt;em&gt;n&lt;/em&gt;/α)⌉, our first result.&lt;/li&gt;
&lt;li&gt;A query for a key that isn't in the set gives a false positive only if its &lt;em&gt;p&lt;/em&gt;-bit fingerprint matches one of the &lt;em&gt;n&lt;/em&gt; stored fingerprints. For a uniform hash, each stored key matches with probability 2^‑&lt;em&gt;p&lt;/em&gt;. Since we want this to be lower than ε, &lt;em&gt;p&lt;/em&gt; = ⌈log₂(&lt;em&gt;n&lt;/em&gt;/α)⌉ and then &lt;em&gt;r&lt;/em&gt; = &lt;em&gt;p&lt;/em&gt;‑&lt;em&gt;q&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given these results, we can now show how to create a QF.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EPSILON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALPHA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createQuotientFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EPSILON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;ALPHA&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// number of values in the QF&lt;/span&gt;

&lt;span class="err"&gt;➋&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;➌&lt;/span&gt;  &lt;span class="na"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isOcc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isCont&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isShifted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we do the calculations ➊ as shown before; a good idea is to add defensive checks to avoid wrong parameters. For us ➋ a QF will be an object with all the dimensions ( &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;q&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt;, and &lt;code&gt;s&lt;/code&gt; ) and &lt;code&gt;c&lt;/code&gt; to count how many values are in the QF; we will want to avoid overloading the structure with too many values. Finally ➌ the slots array includes the &lt;code&gt;rem&lt;/code&gt; field to store the remainders, and the three metadata bits we described previously. (See question 11½–5 for a possible, but incorrect, way to simplify the code.)&lt;/p&gt;

&lt;p&gt;We will also introduce the fingerprint function, which depends on the calculated values for &lt;em&gt;p&lt;/em&gt;, &lt;em&gt;q&lt;/em&gt;, and &lt;em&gt;r&lt;/em&gt;. We will use the &lt;code&gt;hashWithSeed&lt;/code&gt; function from earlier articles to produce an array with quotient and remainder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fingerprintOf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashWithSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;➋&lt;/span&gt;  &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bitwise operations simplify the work:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the quotient is found ➊ by first masking out all bits except the rightmost &lt;code&gt;p&lt;/code&gt; ones, and then shifting right to drop &lt;code&gt;r&lt;/code&gt; bits, leaving just &lt;code&gt;q&lt;/code&gt; bits, and&lt;/li&gt;
&lt;li&gt;the remainder is found ➋ by masking out all bits except the rightmost &lt;code&gt;r&lt;/code&gt; ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can now create a QF; let's work out how to search it, as described in the previous article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Searching a Quotient&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;Let's see the code to find a value. To make the logic clearer, we'll be using a few auxiliary functions that will also come in handy for other operations on a QF.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;➊&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapSlot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;➋&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSlotEmpty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isOcc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isCont&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isShifted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First ➊ we need a function to avoid going out of the array bounds when moving forwards or backward, taking into account that we are wrapping around; the calculation used takes any index (even -1) and forces it into the correct range. We will also write a function to determine if a slot is totally empty ➋ by checking if all its metadata bits are unset. With these two functions under our belt, we can now do a search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fingerprintOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;➋&lt;/span&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isOcc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;➌&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;locateRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;➍&lt;/span&gt;  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt;➎&lt;/span&gt;      &lt;span class="nf"&gt;isSlotEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="err"&gt;➏&lt;/span&gt;      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;runStart&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isCont&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="err"&gt;➐&lt;/span&gt;      &lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rem&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;rem&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;➑&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rem&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➒&lt;/span&gt;    &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapSlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start the search ➊ we find the fingerprint for the sought value, a quotient and remainder, and if there were no values for that quotient ➋ we know the value isn't present. We use an auxiliary &lt;code&gt;runStart&lt;/code&gt; function ➌ to find the first slot in the run that contains the quotient; we'll see it below. After that ➍ we start walking forward. If we reach an empty slot ➎ we walk past the end of the current run ➏, or we are at the right run but exceeding the sought remainder ➐ then the search fails. (For an important detail, see question 11½–6.) If we find the expected remainder ➑ the search succeeds, and in other cases ➒ we just keep advancing.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;add&lt;/code&gt; function, we ensure the table is never 100% full; otherwise, we'd need checks to avoid looping back to the start of the search. (In fact, we'll force its load factor to always be below α to ensure good performance.) Now, let's see the &lt;code&gt;locateRun&lt;/code&gt; function.&lt;br&gt;
&lt;br&gt;
&amp;nbsp;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="err"&gt;➊&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findClusterStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isShifted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapSlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locateRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➋&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;clusterStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findClusterStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;runNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clusterStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➌&lt;/span&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isOcc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;runNumber&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;➍&lt;/span&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;➎&lt;/span&gt;  &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapSlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;➏&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;runStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clusterStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;currentRun&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;runNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;currentRun&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➐&lt;/span&gt;    &lt;span class="nx"&gt;runStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapSlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;runStart&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;➑&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;runStart&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;isCont&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;➒&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;runStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;First ➊ we have a &lt;code&gt;findClusterStart&lt;/code&gt; that just looks for the start of the cluster that includes a given slot; it just goes backward until it reaches a slot with the &lt;code&gt;isShifted&lt;/code&gt; bit unset. Now we can study &lt;code&gt;locateRun&lt;/code&gt;. After determining the cluster start ➋ it moves forward ➎  until reaching the target quotient slot. Every time it encounters an occupied slot ➌ it increments &lt;code&gt;runNumber&lt;/code&gt;; on exiting the loop ➍ this variable tells us the ordinal number of the quotient's run within the cluster. (This is not the actual slot yet; it is only the count of occupied runs encountered before the target quotient.)&lt;/p&gt;

&lt;p&gt;Now ➏ the function starts from the cluster start and advances through runs. The crucial things to remember are that a run may contain multiple occupied slots, and  continuation slots have &lt;code&gt;isCont&lt;/code&gt; set, so the code skips those slots ➐  until it reaches the first one of the next real run ➑. At the end ➒ &lt;code&gt;runStart&lt;/code&gt; is the first slot of the run associated with &lt;code&gt;quot&lt;/code&gt; as we wished.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In the next article in the series, we will study how to maintain Quotient Filters, by adding and removing&amp;nbsp;values.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11½–5. Why not make it simpler?&lt;/strong&gt; We could have written the return in the &lt;code&gt;createQuotientFilter&lt;/code&gt; function as follows; why would it have been a bad idea?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isOcc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isCont&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isShifted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;11½–6. Unneeded test?&lt;/strong&gt; We know that the QF will never be full, so why do we need the test for &lt;code&gt;slot&amp;nbsp;!== runStart&lt;/code&gt; at ➐ in the &lt;code&gt;find&lt;/code&gt; code?&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Quotient Filters - the Theory</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Wed, 14 Oct 2026 18:30:33 +0000</pubDate>
      <link>https://dev.to/fkereki/part-6-quotient-filters-the-theory-228a</link>
      <guid>https://dev.to/fkereki/part-6-quotient-filters-the-theory-228a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the previous articles of this series (&lt;a href="https://dev.to/fkereki/filters-and-probabilistic-searching-part-2-bloom-filters-the-theory-441k"&gt;from here onwards&lt;/a&gt;) we saw Bloom Filters, with restrictions on removing added values. Here we'll see Quotient Filters, which free the caller from those&amp;nbsp;limits.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Quotient Filters
&lt;/h2&gt;

&lt;p&gt;Bloom Filters answer one question cheaply: "Have I possibly seen this value before?" An SBF cannot support removals, and a CBF can, albeit at a cost: it requires more memory, and to ensure a removal causes no problems, the caller must do extra work. Quotient Filters (QF) work differently to avoid these issues.&lt;/p&gt;

&lt;p&gt;A QF computes a single hash per item and splits it into two parts: a &lt;em&gt;quotient&lt;/em&gt; that determines a &lt;em&gt;slot&lt;/em&gt; in an array, and a &lt;em&gt;remainder&lt;/em&gt;, which is stored in that slot as a small fingerprint. Because both the quotient and remainder come from a single source rather than several unrelated ones, the structure preserves enough information to support removals, using about the same memory as a comparable Bloom Filter.&lt;/p&gt;

&lt;p&gt;Since the structure may be intimidating, let's start with an example of a built QF and how to search it, and we'll leave additions and removals for upcoming articles.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Structure of Quotient&amp;nbsp;Filters
&lt;/h3&gt;

&lt;p&gt;A QF is, in some aspects, similar to a Hash Table with Open Addressing (see Chapter 11 for more on that) but there are some differences. In essence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Depending on how many values (&lt;em&gt;n&lt;/em&gt;) you want to store, a size (&lt;em&gt;s&lt;/em&gt;) is determined. Traditionally, the size is always a power of 2 so you can use bitwise operations for speed. A QF consists of &lt;em&gt;s&lt;/em&gt; slots, each including content and three additional metadata bits. The table is used in circular fashion, so after the last element you loop back to the table's first.&lt;/li&gt;
&lt;li&gt;For each value to store, we compute a &lt;em&gt;p&lt;/em&gt;-bit "fingerprint" hash, then split it into a &lt;em&gt;q&lt;/em&gt;-bit quotient (such that 2^&lt;em&gt;q&lt;/em&gt; = &lt;em&gt;s&lt;/em&gt;) and an &lt;em&gt;r&lt;/em&gt;-bit remainder; we have &lt;em&gt;p&lt;/em&gt; = &lt;em&gt;q&lt;/em&gt; + &lt;em&gt;r&lt;/em&gt;. We will use the quotient as an index to determine the canonical/home slot where we would want to store the remainder. As with hash tables, the slot may already be in use, so we may need to move elements around.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a QF, all remainders corresponding to the same quotient will be stored contiguously, in ascending order, in one &lt;em&gt;run&lt;/em&gt;. If we have two or more runs such that each one immediately follows the previous one, we call that sequence a &lt;em&gt;cluster&lt;/em&gt;. Runs within a cluster appear in increasing order of their quotients.&lt;/p&gt;

&lt;p&gt;You may be wondering how the structure works if we store only remainders. A good question, but we'll also have three metadata bits for each slot, and they will allow us to link each remainder (&lt;em&gt;rem&lt;/em&gt;) to the corresponding quotient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isOcc[j]&lt;/code&gt; ("is occupied") is true if there is at least one value (stored somewhere in the table that corresponds to the quotient &lt;code&gt;j&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isCont[j]&lt;/code&gt; ("is continuation") is true if the element physically in slot &lt;code&gt;j&lt;/code&gt; is not the first element of its run.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isShifted[j]&lt;/code&gt; is true if the element at slot &lt;code&gt;j&lt;/code&gt; is not in its own canonical slot; it had to be displaced.&lt;/li&gt;
&lt;li&gt;A totally empty, unused slot has its three metadata bits unset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've had several definitions; let's look at an actual QF and study how remainders and metadata bits work. Don't worry now about how the table was created; we'll get to that later.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Example of Quotient&amp;nbsp;Filters
&lt;/h3&gt;

&lt;p&gt;A sample QF would look like the one shown in Figure 11½–11.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzisi7pi9op95q2we1hap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzisi7pi9op95q2we1hap.png" alt="Figure 11½–11: A QF, with 4 runs in 12 slots" width="799" height="197"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–11:&lt;/strong&gt; A QF, with 4 runs in 12 slots. Slot for q=15 starts at the right and wraps&amp;nbsp;around.&lt;/p&gt;

&lt;p&gt;The size of a QF is usually a power of 2; here we have 16 slots, corresponding to &lt;em&gt;q&lt;/em&gt;=4 bits. We are not showing actual remainders, but assume they require &lt;em&gt;r&lt;/em&gt;=5 bits so we can show letters (A=1, B=2, C=3, etc.) to simplify. In our case, then &lt;em&gt;p&lt;/em&gt;=9.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After creating the empty QF, we added values E, G, K, and L for quotient 4; B and D for quotient 5; C, F, and W for quotient 7; and U, Y, and Z for quotient 15. In practice, the same remainder will often occur for different quotients, but to make the example clearer, we assume all remainders are distinct.&lt;/li&gt;
&lt;li&gt;The top row, &lt;em&gt;slot #&lt;/em&gt;, just numbers the slots from 0 to 15. Remember that the table is circular, so slot 0 follows slot 15.&lt;/li&gt;
&lt;li&gt;The second row, &lt;em&gt;isOcc&lt;/em&gt;, shows for which quotients there is at least one value. Positions 4, 5, 7, and 15 are set, and all the rest are not.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;The third row, &lt;em&gt;isCont&lt;/em&gt;, is set when the element corresponds to the same run as the immediately previous one.&lt;/li&gt;
&lt;li&gt;The last row, &lt;em&gt;isShifted&lt;/em&gt;, shows whether the value is in its home slot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's figure out the runs, and that will make everything clearer.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For quotient 4, &lt;em&gt;isCont&lt;/em&gt; and &lt;em&gt;isShifted&lt;/em&gt; are both 0, so the run starts at slot 4. Slots 5 through 7 have &lt;em&gt;isCont&lt;/em&gt; set, so those three values are in the same run. The next slot (8) has &lt;em&gt;isCont&lt;/em&gt; not set, so that's a different run. We conclude that the run for quotient 4 includes E, G, K, and L.&lt;/li&gt;
&lt;li&gt;For quotient 15, it's the same case. The only thing to keep in mind is that the table is circular, so the U at slot 15 is followed by Y and Z at slots 0 and 1.&lt;/li&gt;
&lt;li&gt;For quotient 5, &lt;em&gt;isShifted&lt;/em&gt; is set, so the value there corresponds to a different quotient, not 5. To find the start of the run we want, we must first go back until we find a value with &lt;em&gt;isShifted&lt;/em&gt; unset, which appears at slot 4. Then we move right along both the &lt;em&gt;isOcc&lt;/em&gt; and &lt;em&gt;isShifted&lt;/em&gt; rows, finding that the run for quotient 4 starts at slot 4, and the run for quotient 5 starts at slot 8. Now, moving right to slot 10, where isCont is not set, we deduce that the run for quotient 5 includes B and D.&lt;/li&gt;
&lt;li&gt;For quotient 7, the case is similar. Go left to the start of the run for quotient 4, then move right past the start of the run for quotient 5 at slot 8, and find the run for quotient 7 from slots 10 to 12, including C, F, and W.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operations with a Quotient&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;Now we can sketch how basic operations work; we'll see actual code in the following articles in this series. For starters, how do we search for a value? The logic is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the hash function to determine the quotient and remainder for the value;&lt;/li&gt;
&lt;li&gt;Locate the run for that quotient; if there's no run, the value isn't in the QF.&lt;/li&gt;
&lt;li&gt;Start scanning the run for the remainder; if you find the value, the search was successful, but if you go past the end of the run or reach a value higher than your remainder, the search is unsuccessful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add a new value, the logic would run along the lines of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the hash function to determine the quotient and remainder for the value;&lt;/li&gt;
&lt;li&gt;If there is no run for that quotient and the corresponding slot is empty, just store the remainder there and set the metadata bits accordingly.&lt;/li&gt;
&lt;li&gt;If there's a run for that quotient, you'll have to locate it, determine where the new value should go, and "bump" the following values forward. This may force changing metadata for values in other runs.&lt;/li&gt;
&lt;li&gt;If there's no run for that quotient, you'll have to find the end of the previous run, move other values forward, and fix metadata bits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, removing a value requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the corresponding run for the quotient and the slot for the remainder;&lt;/li&gt;
&lt;li&gt;Remove the value and move the following values to the left, fixing metadata bits if needed.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are just sketches; in the next article we'll cover creating and searching a QF, and then we'll look at the (longer, more complex) code for adding and removing values.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In the &lt;a href="https://dev.to/fkereki/part-7-quotient-filters-creating-and-searching-3of8"&gt;next article in the series&lt;/a&gt;, we will study how to size, create, and search Quotient&amp;nbsp;Filters.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>datastructures</category>
      <category>algorithms</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Counting Bloom Filters</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Mon, 12 Oct 2026 16:17:08 +0000</pubDate>
      <link>https://dev.to/fkereki/part-5-counting-bloom-filters-4230</link>
      <guid>https://dev.to/fkereki/part-5-counting-bloom-filters-4230</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In &lt;a&gt;previous&lt;/a&gt; &lt;a href="https://dev.to/fkereki/part-4-double-hashing-bloom-filters-31l5"&gt;articles&lt;/a&gt; in this series, we saw Bloom Filter implementations that didn't allow removing values without later producing wrong answers. Here we'll see Counting Bloom Filters, which (with an important restriction) allow both adding and removing&amp;nbsp;values.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Counting Bloom&amp;nbsp;Filters
&lt;/h2&gt;

&lt;p&gt;In the previous parts, we considered Standard Bloom Filters and Double Hashing Bloom Filters. We saw how to create, update, and search them, but eventually realized we couldn't remove values from the filter without causing "collateral damage" and making the filter produce wrong answers. In this part we'll show a variant, a Counting Bloom Filter, that (with an important restriction) allows both adding and removing values.&lt;/p&gt;

&lt;p&gt;When we tried to remove a value from a Standard Bloom Filter, we found that we could reset a bit set by other additions. The solution we can consider - and let's just consider additions and searches for now - is:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of an array of booleans, we will work with an array of counters.&lt;/li&gt;
&lt;li&gt;When we add a value to the filter, we will increment the corresponding counters by one, as determined by the hash functions.&lt;/li&gt;
&lt;li&gt;When we search for a value, instead of checking for true bits, we check whether all the counters are nonzero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see how we'd implement this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sizing a Counting Bloom&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;When sizing a CBF, there's an extra factor to consider. Since the CBF just differs from a BF because it has a count map instead of a bit map, determining the size of the bit map and the number of hash functions to use require exactly the same calculations; nothing new here. However, there's an extra detail: how large should the counts be, what's the maximum value we should expect?&amp;nbsp;&lt;/p&gt;

&lt;p&gt;To save memory, we want to use the smallest possible counters, in bits. However, we can't guarantee that, in some cases, the added values won't cause a counter to overflow. We will pick a size that keeps the overflow probability acceptably low, so we can basically ignore the issue.&lt;/p&gt;

&lt;p&gt;The basic approach would start by observing that the average number of "hits" for a given slot is hn/b, where h, n, and b are as before: number of hashes, number of values to store, and size of the count map. To consider instead the maximum count gets into probabilistic arguments involving Poisson distributions, which we won't get into, but there are published results that show that for the sizing formulae we are using, 4-bit counters (counts 0 to 15) have a probability of overflow on the order of 10^(‑15); essentially ignorable. If you may have insertions with heavy duplication (same item inserted again and again) then 5‑6 bits (counts up to 31 or 63) are usually overkill.&lt;/p&gt;

&lt;p&gt;Even so! How would you deal with a possible overflow? We'll leave that topic to question 11½–4, because the change isn't hard, and we'll focus on simpler logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating and Adding to a Counting Bloom&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;We can start with the BF code. We just have to change how we create the filter and how we use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newCountingBloomFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EPSILON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;LN2_SQ&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;LN2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt;  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➋&lt;/span&gt;  &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;➌&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are only three changes with regard to what we saw earlier. When we initialize the array ➊ we fill it with zeroes instead of false. On adding a value ➋ we increment the counter instead of setting it  to true. Finally, when searching ➌ non-zero counts are equivalent to a set bit.&lt;/p&gt;

&lt;p&gt;Going back to our BF example, Figure 11½–7 below shows how the counts would look after adding three of the Marx brothers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhmi60cnofjkqkx7srne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhmi60cnofjkqkx7srne.png" alt="Figure 11½–7: Three additions to a CBF, using a count map instead of a bit&amp;nbsp;map" width="537" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–7:&lt;/strong&gt; Three additions to a CBF, using a count map instead of a bit&amp;nbsp;map&lt;/p&gt;

&lt;p&gt;If you compare this with Figure 11½–2, the involved hash-calculated positions are the same, but now when two or more values hash to the same place, we increment the count instead of setting a bit.&lt;/p&gt;
&lt;h3&gt;
  
  
  Removing a value from a Counting Bloom&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;Now we could implement a remove operation as follows - but there's a hidden potential issue!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➋&lt;/span&gt;  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic seems straightforward. We first check ➊ if the value is in the filter, and if so ➋ we decrement all its counters; what could go wrong? Remember that &lt;code&gt;find&lt;/code&gt; may produce false positives; even if it returns true, the value may not have been added to the filter, and you would decrement counts for other values, even producing false negatives; bad!&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For instance, we earlier saw that GUMMO produced a false positive. If we use the logic above, the resulting situation would be as shown in Figure 11½–8 below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pwk2p8zqj2l29aqzyt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pwk2p8zqj2l29aqzyt9.png" alt="Figure 11½–8: Removing GUMMO, a false positive, generates a&amp;nbsp;problem.&lt;br&gt;
" width="537" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–8:&lt;/strong&gt; Removing GUMMO, a false positive, generates a&amp;nbsp;problem.&lt;/p&gt;

&lt;p&gt;But what happens now if we search for CHICO, which we added to the filter? Figure 11½–9 shows that this produces a false negative; a no-no!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fni3gkz3mwcs41t7waq00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fni3gkz3mwcs41t7waq00.png" alt="Figure 11½–9: After removing GUMMO we get a false negative for&amp;nbsp;CHICO." width="537" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–9:&lt;/strong&gt; After removing GUMMO we get a false negative for&amp;nbsp;CHICO.&lt;/p&gt;

&lt;p&gt;The issue is clear: as with a BF, we shouldn't remove a value unless we're sure it was added to the filter, and we can't rely on a find operation because it may produce false positives, as in the case we just showed.&lt;/p&gt;

&lt;p&gt;This means we can use a CBF for removals only if we have a secondary way to ensure the value to be removed was actually inserted. This, of course, means the caller is responsible for keeping the structure healthy, and that's either a risk (if done wrongly) or a costly chore (if done rightly). Figure 11½–10 shows a possible flowchart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqmst86zyifu5op4otqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqmst86zyifu5op4otqy.png" alt="Figure 11½–10: Using a secondary store for checks allows successful removals" width="781" height="638"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–10:&lt;/strong&gt; Using a secondary store for checks allows successful removals&lt;/p&gt;

&lt;p&gt;With this approach, counts guarantee you never produce false negatives: adding a value increments some counts, removing that value decrements the same counts, and no other counts are affected.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A CBF is thus a first approximation to a Bloom Filter that allows removals, with high performance but a separate risk, or the need for a secondary test. We will move on to other implementations that may be costlier or more complicated but place fewer restrictions on users. Next, we'll consider the first option: &lt;em&gt;Quotient Filters&lt;/em&gt;.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In the &lt;a href="https://dev.to/fkereki/part-6-quotient-filters-the-theory-228a"&gt;next article in the series&lt;/a&gt;, we will study Quotient Filters, which allow removals with fewer&amp;nbsp;issues.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11½–4. Over the top?&lt;/strong&gt; The usual solution to counter overflows is to check the count before adding a value; if it's already at the maximum, don't modify it. When removing a value, do a similar test, and if the count is at maximum, do not decrement it. (See the changed logic below.) What are the consequences of this logic in terms of false positives and false negatives?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_VALUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 4-bit counters assumed&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>algorithms</category>
      <category>computerscience</category>
      <category>datastructures</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Double Hashing Bloom Filters</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Sat, 10 Oct 2026 17:29:26 +0000</pubDate>
      <link>https://dev.to/fkereki/part-4-double-hashing-bloom-filters-31l5</link>
      <guid>https://dev.to/fkereki/part-4-double-hashing-bloom-filters-31l5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the &lt;a href="https://dev.to/fkereki/part-3-bloom-filters-the-practice-6b3"&gt;previous article in this series&lt;/a&gt;, we implemented Bloom Filters, and now we'll see a better-performing version, Double Hashing Bloom&amp;nbsp;Filters.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Double Hashing Bloom&amp;nbsp;Filters
&lt;/h2&gt;

&lt;p&gt;In the BF implementation we saw, we calculated the required bit positions by repeatedly calling a full SHA-256 hash function, changing only the seed each time. The code we used was as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getIndices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;hashWithSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well, but it's a bit wasteful. Every call to &lt;code&gt;hashWithSeed&lt;/code&gt; rehashes the entire value from scratch, and SHA-256, being a cryptographic hash, is deliberately expensive to compute because it's meant as a security feature. For a large Bloom filter, this may become a bottleneck.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;However, one insight avoids the need for so many distinct, independent hashes. We can borrow a technique from hash tables, double hashing (see the "&lt;em&gt;Double Hashing&lt;/em&gt;" section in Chapter 11 of the book) and get all the needed bit positions from just two hash calculations. The Kirsch-Mitzenmacher technique, named after the authors of a 2006 paper that developed the method, does that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Double Hashing Bloom&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;The core idea for our Double Hashing Bloom Filter (DHBF) is to compute two independent hashes, h1 and h2, and derive the h needed values as in double hashing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index_i = (h1 + i * h2) mod m, for i = 0, 1, …, h-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with searching, we start at bit &lt;code&gt;h1&lt;/code&gt; and then advance (cyclically) in &lt;code&gt;h2&lt;/code&gt;-sized steps; &lt;code&gt;h2&lt;/code&gt; must be non-zero. The new getIndices function is now as follows - but we'll have to consider that not all &lt;code&gt;h2&lt;/code&gt; values work!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getIndices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashWithSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="err"&gt;➊&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashWithSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is exactly as we saw before; the only difference ➊ is that we make sure &lt;code&gt;h2&lt;/code&gt; is not zero. Should &lt;code&gt;hashWithSeed&lt;/code&gt; return 0, 1 would be used instead.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;If you remember what we saw when we first studied double hashing (see the "&lt;em&gt;Adding a Value to a Table That Uses Double Hashing&lt;/em&gt;" section in Chapter 11) a bad choice of &lt;code&gt;h2&lt;/code&gt; may cause loops, which we have to prevent. In terms of our logic here, &lt;code&gt;h2&lt;/code&gt; and the size of the table (&lt;code&gt;filter.b&lt;/code&gt;) must have no common factors. The simplest solution is to ensure the table length is prime, which leads to the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newDoubleHashingBloomFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EPSILON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ln2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findNextPrime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ln2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ln2&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ln2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is the same as before, but we now force &lt;code&gt;b&lt;/code&gt; to be prime ➊ as we did in the "&lt;em&gt;Creating a Table That Uses Double Hashing with Prime Lengths&lt;/em&gt;" section in the previous chapter of the book. (Another possibility exists; see question 11½–3.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a value to a Double Hashing Bloom&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;The logic for adding a value to a DHBF is the same as for SBF, because it depends on whatever &lt;code&gt;getIndices&lt;/code&gt; returns. Let's just repeat the code we saw previously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the Marx brothers example, we earlier had a bit array with 10 elements. Since 10 is not prime, we'd use 11 bits instead. For clarity, let's assume the hash functions return the same values as before, though they are called with different parameters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We'd calculate just two hashes for GROUCHO, 2 and 7, so the bits to set would be 2, 9 (2+7), and 5 (9+7 mod 11).&lt;/li&gt;
&lt;li&gt;For HARPO, the first two hashes are 6 and 5, so we would set bits 6, 0 (6+5 mod 11), and 5 (0+5).&lt;/li&gt;
&lt;li&gt;Finally, for CHICO, the hashes are 1 and 9, so we set bits 1, 10 (1+9), and 8 (10+9 mod 11).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a DHBF, we'd end with bits set at positions 0, 1, 2, 5, 6, 8, 9, and 10.&lt;/p&gt;

&lt;h3&gt;
  
  
  Searching for a value in a Bloom&amp;nbsp;Filter
&lt;/h3&gt;

&lt;p&gt;Given that we have delegated calculating the bit positions to use to the &lt;code&gt;getIndices&lt;/code&gt; function, the same code we used for SBF works here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To rework the examples from the previous section:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Searching for CHICO would check bits 1, 10, and 8, just as we saw above, and finding all set to 1 would return a "maybe".&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Searching for ZEPPO would check bits 3, 10, and 6 (can you work out why?) so it would return a "no"; bit 3 is not set.&lt;/li&gt;
&lt;li&gt;Searching for GUMMO would check bits 1, 3, and 5; in this case, the search would return a "no" instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One additional point to highlight: With an SBF, there's a clear possibility that some of the hashes will be repeated. Repeated bit positions help produce more false negatives: if you check fewer bits, your probability of finding a zero is lower. With a DHBF, the way we generate the indices of the bits to test ensures we will always have a set of distinct positions, making the structure work better.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In the &lt;a href="https://dev.to/fkereki/part-5-counting-bloom-filters-4230"&gt;next article in the series&lt;/a&gt;, we will see Counting Bloom Filters, which allow removals under some conditions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11½–3. A powerful solution&lt;/strong&gt;. Show that if you force the length of the bit table to be a power of 2 and &lt;code&gt;h2&lt;/code&gt; to be odd, there will be no loops. Is this a practical solution? Why or why not? Show the code to implement this.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Bloom Filters - the Practice</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Fri, 09 Oct 2026 11:25:37 +0000</pubDate>
      <link>https://dev.to/fkereki/part-3-bloom-filters-the-practice-6b3</link>
      <guid>https://dev.to/fkereki/part-3-bloom-filters-the-practice-6b3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the &lt;a href="https://dev.to/fkereki/filters-and-probabilistic-searching-part-2-bloom-filters-the-theory-441k"&gt;previous article in this series&lt;/a&gt;, we covered the theory of sizing and using Bloom Filters; now let's actually implement them with full JavaScript code.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Creating a Bloom&amp;nbsp;Filter
&lt;/h2&gt;

&lt;p&gt;A BF will be implemented as an object with three properties: &lt;code&gt;b&lt;/code&gt;  and &lt;code&gt;h&lt;/code&gt; as described above, and &lt;code&gt;bits&lt;/code&gt;, an array with &lt;code&gt;b&lt;/code&gt; bit flags, all initially set to 0. (It could be argued that a BF user need not know its internals; see question 11½‑1.) We could skip including &lt;code&gt;b&lt;/code&gt; since it's just the length of the bits array, but let's not worry about that now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EPSILON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LN2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;➋&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LN2_SQ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LN2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;LN2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newStandardBloomFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eps&lt;/span&gt;
&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/655r2sekwgs7q1x4ppd8.png)&lt;/span&gt;

 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EPSILON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;➌&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;LN2_SQ&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;➍&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;LN2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just as a minimal optimization, we define two constants ➊ ➋ to avoid redoing calculations if we create more than one filter. Then ➌ ➍ we use those values to directly implement the results we found. We can now create a BF; now let's see how to update it. (See question 11½‑2 for an optimization as to memory usage.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a value to a Bloom&amp;nbsp;Filter
&lt;/h2&gt;

&lt;p&gt;How do we add a value to a BF? We'll need a function to produce a hash value from a string. The result should be between 0 and &lt;em&gt;b&lt;/em&gt;-1. Using that function, we will write a &lt;code&gt;getIndices&lt;/code&gt; function that will return the &lt;em&gt;h&lt;/em&gt; positions that correspond to the string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashWithSeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;crypto&lt;/span&gt;
&lt;span class="err"&gt;➊&lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//dev.to/fkereki/part-4-double-hashing-bloom-filters-31l5createHash("sha256")&lt;/span&gt;
&lt;span class="err"&gt;➋&lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;➌&lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readUInt32BE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getIndices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;➍&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
     &lt;span class="nf"&gt;hashWithSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hash generation uses SHA-256 ➊ to produce a stable, cryptographic fingerprint for each value and seed pair. It combines the seed and the item ➋ as a string like "2:GROUCHO", hashes that, and then takes the first 32 bits of the digest and reduces it modulo the Bloom filter's bit-array size ➌. This gives a deterministic index to the bit array, which is exactly what we need. Finally ➍ &lt;code&gt;getIndices&lt;/code&gt; generates an array of size &lt;em&gt;h&lt;/em&gt; with all the calculated hashes; each hash is different because we combined the value with distinct seeds.&lt;/p&gt;

&lt;p&gt;Given these functions, adding a value is quite short.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getIndices&lt;/code&gt; function returns an array of the bit positions we need to set, and we do that one by one. We're done! Now, let's do searches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching for a value in a Bloom&amp;nbsp;Filter
&lt;/h2&gt;

&lt;p&gt;With all the code we showed, search can be done in a very concise way, a "one liner" in fact.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;getIndices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As when adding a value, the &lt;code&gt;getIndices&lt;/code&gt; function returns an array with the positions of the bits we have to check, and we use&amp;nbsp;&lt;code&gt;.every&lt;/code&gt; to verify that all those bits are 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deleting a value from a Bloom&amp;nbsp;Filter
&lt;/h2&gt;

&lt;p&gt;If adding a value to a BF just requires setting some bits to 1, you might think that removing a value would imply zeroing those same bits - but that won't work! In fact, you cannot remove a value from a BF without possibly making it produce wrong answers, as we'll see.&lt;/p&gt;

&lt;p&gt;Let's go back to the example with the Marx brothers. Suppose you wanted to remove GROUCHO and then set bits 2, 7, and 4 to zero. Figure 11½–5 below shows the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frtlcul3cqi2afllzyxli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frtlcul3cqi2afllzyxli.png" alt="Figure 11½–5: Removing a value may unset bits that were also set by other previous&amp;nbsp;values" width="507" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–5:&lt;/strong&gt; Removing a value may unset bits that were also set by other previous&amp;nbsp;values&lt;/p&gt;

&lt;p&gt;Now, there's a problem. What happens when you search for HARPO? You would check bits 6, 5, and 2 - and find that bit 2 is now 0! Since all the bits corresponding to an item must be set to 1 for it to be considered present, you'd conclude that HARPO was never added to the Bloom filter; see Figure 11½–6.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6gbmi48p8huth5igq5o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6gbmi48p8huth5igq5o4.png" alt="Figure 11½–6: After removing GROUCHO, a search for HARPO produces a false&amp;nbsp;negative" width="507" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–6:&lt;/strong&gt; After removing GROUCHO, a search for HARPO produces a false&amp;nbsp;negative&lt;/p&gt;

&lt;p&gt;We'll consider Bloom filters that allow deletions, but first we'll study a variant: a &lt;em&gt;Double Hashing Bloom Filter&lt;/em&gt;, which uses a technique that provides better overall performance.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In the &lt;a href="https://dev.to/fkereki/part-4-double-hashing-bloom-filters-31l5"&gt;next article in the series&lt;/a&gt;, we'll look at the better-performing Double Hashing Bloom&amp;nbsp;Filters.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11½‑1. Do it with class!&lt;/strong&gt; In the book, we have always been working in a functional way and used few classes. Can you rewrite the Bloom Filter code to use a class instead? Alternatively, you may want to use closures. The goal here is to hide as many internal details as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11½‑2. Getting down to bits.&lt;/strong&gt; Using an array of numbers wastes a lot of memory, because you just need 1 bit at each position. Rewrite the code using the &lt;code&gt;Unit8Array&lt;/code&gt; type and bitwise operators to minimize memory usage. Each byte in the array should provide 8 bits.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>In my Data Structures and Algorithms in JavaScript book, I discussed Dictionary implementations for exact searches. I am now starting a new series (Chapter 11 !) on Filters and Probabilistic Searching, which you can find at https://tinyurl.com/3kmcvn8u</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Thu, 08 Oct 2026 12:38:09 +0000</pubDate>
      <link>https://dev.to/fkereki/in-my-data-structures-and-algorithms-in-javascript-book-i-discussed-dictionary-implementations-for-59h9</link>
      <guid>https://dev.to/fkereki/in-my-data-structures-and-algorithms-in-javascript-book-i-discussed-dictionary-implementations-for-59h9</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dev.to/fkereki/filters-and-probabilistic-searching-part-1-introduction-4kl8" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4uhl3sw35zckjwj3ohab.png" height="648" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dev.to/fkereki/filters-and-probabilistic-searching-part-1-introduction-4kl8" rel="noopener noreferrer" class="c-link"&gt;
            Part 1: Introducing Filters and Probabilistic Searching - DEV Community
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            In my Data Structures and Algorithms in JavaScript book, we studied Dictionary implementations for...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" width="300" height="299"&gt;
          dev.to
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>algorithms</category>
      <category>computerscience</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Bloom Filters - the Theory</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Thu, 08 Oct 2026 11:46:02 +0000</pubDate>
      <link>https://dev.to/fkereki/filters-and-probabilistic-searching-part-2-bloom-filters-the-theory-441k</link>
      <guid>https://dev.to/fkereki/filters-and-probabilistic-searching-part-2-bloom-filters-the-theory-441k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the &lt;a href="https://dev.to/fkereki/filters-and-probabilistic-searching-part-1-introduction-4kl8"&gt;first article in this series&lt;/a&gt;, we introduced Filters for Probabilistic Searches, and their operations. Here we'll start with Bloom Filters, the first implementation to consider.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Bloom Filters
&lt;/h2&gt;

&lt;p&gt;The first implementation we'll look at is a Bloom Filter (BF). The name has nothing to do with flowers or how the filter behaves; the "Bloom" part comes from Burton H. Bloom, a computer scientist who introduced the structure in a 1970 paper published in &lt;em&gt;Communications of the ACM&lt;/em&gt;, "Space/Time Trade-offs in Hash Coding with Allowable Errors".&lt;/p&gt;

&lt;p&gt;Interestingly, the structure was a relatively obscure theoretical curiosity for decades before becoming widely used several decades later, when memory-constrained, large-scale systems needed exactly the space/accuracy trade-off Bloom described.&lt;/p&gt;

&lt;p&gt;The core idea is to use a bit array and several independent hash functions to record a "fingerprint" of added items. Basically, how you add an item is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let the bit array size be &lt;em&gt;b&lt;/em&gt;, and the number of hash functions be &lt;em&gt;h&lt;/em&gt;. Each hash function maps its input to one of the &lt;em&gt;b&lt;/em&gt; positions.&lt;/li&gt;
&lt;li&gt;Apply the hash functions to the input, producing &lt;em&gt;h&lt;/em&gt; positions for the array. (Yes, some of the &lt;em&gt;h&lt;/em&gt; values may be repeated, but ignore this for the moment.)&lt;/li&gt;
&lt;li&gt;Set the bit at each of those positions to 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see an example. Say &lt;em&gt;b&lt;/em&gt;=10 and &lt;em&gt;h&lt;/em&gt;=3, and we want to add three of the Marx brothers, GROUCHO, HARPO, and CHICO, to the filter.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applying the three hashes to GROUCHO may produce 2, 7, and 4; we set the corresponding bits to 1.&lt;/li&gt;
&lt;li&gt;Suppose applying the hashes to HARPO produces 6, 5, and 2; we now have bits 2, 4, 5, 6, and 7 set. Note that bit 2 was already set; it stays that way.&lt;/li&gt;
&lt;li&gt;Applying hashes to CHICO produces 1, 9, and 6; we have bits 1, 2, 4, 5, 6, 7, and 9 set.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the following figure for a summary of the three additions; dotted gray lines show bits set more than once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fel11kj59l85ebvrczjvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fel11kj59l85ebvrczjvx.png" alt="Figure 11½–2: Three additions to a Bloom&amp;nbsp;Filter" width="507" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–2:&lt;/strong&gt; Three additions to a Bloom&amp;nbsp;Filter&lt;/p&gt;

&lt;p&gt;Now for the interesting part: how do we search for a string? The way to do a search is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apply the hash functions to the input you want to find, producing &lt;em&gt;h&lt;/em&gt; bit positions.&lt;/li&gt;
&lt;li&gt;Check those positions: if any of them (at least one) is not set, the input is definitely NOT in the structure.&lt;/li&gt;
&lt;li&gt;If all are set, the input may be in the structure, but it could be a false positive!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see some examples. We want to check ZEPPO, CHICO, and GUMMO. First, when we search for ZEPPO, assume the hash functions produce 3, 7, and 8. Since at least one of those bits is 0, we can assert that ZEPPO was never added and is not present. The figure below shows the search, with dotted gray lines indicating the unset bits found. Practically, you wouldn't continue checking after finding the first unset bit; a single 0 is enough to declare the search failed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fufgmu2bnkizonn2qfo8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fufgmu2bnkizonn2qfo8d.png" alt="Figure 11½–3: A failed search; one unset bit is&amp;nbsp;enough." width="507" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–3&lt;/strong&gt;: A failed search; one unset bit is&amp;nbsp;enough.&lt;/p&gt;

&lt;p&gt;Now let's see some possibly successful searches. When we search for CHICO, the hashes produce 1, 9, and 6, and all those bits are set to 1. Suppose that when we search for GUMMO, suppose the hash functions produce 1, 2, and 4; again, all those bits are set to 1. We cannot definitely say the searches were successful; in one case (CHICO), the bits were set because we added that value, but in the other case (GUMMO), other values set the bits.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbq8h8z3bpsv9w9qu67u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbq8h8z3bpsv9w9qu67u.png" alt="Figure 11½–4: Both searches find all bits set, but only one value was actually&amp;nbsp;added." width="507" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½–4&lt;/strong&gt;: Both searches find all bits set, but only one value was actually&amp;nbsp;added.&lt;/p&gt;

&lt;p&gt;We now know how to use a filter; we need to decide how to size it before we can write the code for additions and searches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sizing a Bloom&amp;nbsp;Filter
&lt;/h2&gt;

&lt;p&gt;As we mentioned in the previous section, to appropriately size a BF, you must know &lt;em&gt;n&lt;/em&gt;, how many elements to insert, and the false-positive rate, ε, and you'll decide how many bits to use (&lt;em&gt;b&lt;/em&gt;) and how many hash functions are needed (&lt;em&gt;h&lt;/em&gt;). By the way, ε is usually set at 0.01, 1%, or much less. The lower the value, the fewer the false positives.&lt;br&gt;
&amp;nbsp;&lt;br&gt;
Let's do some math; if you wish, skip ahead! If you insert one element, each of the &lt;em&gt;h&lt;/em&gt; functions sets a bit. The probability that any insertion does not hit a particular bit is 1‑1/&lt;em&gt;b&lt;/em&gt;, so after &lt;em&gt;n&lt;/em&gt; insertions with &lt;em&gt;h&lt;/em&gt; hashes each, the probability that the bit is still not set would be (1‑1/&lt;em&gt;b&lt;/em&gt;)^&lt;em&gt;hn&lt;/em&gt;. For large &lt;em&gt;b&lt;/em&gt;, this is approximately &lt;em&gt;e&lt;/em&gt;^(‑&lt;em&gt;hn&lt;/em&gt;/&lt;em&gt;b&lt;/em&gt;). You get a false positive if you search for a value that was never inserted, but all the corresponding &lt;em&gt;h&lt;/em&gt; bits happen to be set. This false-positive probability is (1‑&lt;em&gt;e&lt;/em&gt;^(‑&lt;em&gt;hn&lt;/em&gt;/&lt;em&gt;b&lt;/em&gt;))^&lt;em&gt;h&lt;/em&gt;, and from this result we'll get the sizes we need.&lt;/p&gt;

&lt;p&gt;Let's find how many hashes we need. Fixing &lt;em&gt;b&lt;/em&gt; and &lt;em&gt;n&lt;/em&gt;, the formula depends only on &lt;em&gt;h&lt;/em&gt;, and is optimized when &lt;em&gt;h&lt;/em&gt; is (&lt;em&gt;b&lt;/em&gt;/&lt;em&gt;n&lt;/em&gt;) ln(2). Substituting this value into the false-positive formula and simplifying eventually produces 2^(‑(&lt;em&gt;b&lt;/em&gt; ln(2)/&lt;em&gt;n&lt;/em&gt;)). Equating this to ε, &lt;em&gt;b&lt;/em&gt; should be ‑(&lt;em&gt;n&lt;/em&gt; ln(ε))/ln²(2). At this point, we have everything we need to start writing code; let's get to it.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In &lt;a href="https://dev.to/fkereki/part-3-bloom-filters-the-practice-6b3"&gt;the next article in the series&lt;/a&gt;, we will see how to implement the Bloom Filters we described here, with full JavaScript code for all algorithms.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Filters and Probabilistic Searching</title>
      <dc:creator>Federico Kereki</dc:creator>
      <pubDate>Thu, 08 Oct 2026 11:36:32 +0000</pubDate>
      <link>https://dev.to/fkereki/filters-and-probabilistic-searching-part-1-introduction-4kl8</link>
      <guid>https://dev.to/fkereki/filters-and-probabilistic-searching-part-1-introduction-4kl8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In my &lt;a href="https://www.amazon.com/Structures-Algorithms-JavaScript-Federico-Kereki-ebook/dp/B0B7R286TN" rel="noopener noreferrer"&gt;Data Structures and Algorithms in JavaScript&lt;/a&gt; book, we studied Dictionary implementations for exact searches. In this article series, we will explain what probabilistic searches are, when they may be needed, and cover Filters, a way to do these searches, showing several data structures with complete JavaScript code, plus examples and even questions for you to work out. This could have been an addendum to Chapter 11 in the book (new title: "&lt;strong&gt;Bags, Sets, Maps, and Filters&lt;/strong&gt;"?) but I prefer a new number (&lt;strong&gt;Chapter 11½&lt;/strong&gt;!) to not modify existing chapter&amp;nbsp;numbers.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introducing Filters
&lt;/h2&gt;

&lt;p&gt;A common problem in Computer Science is determining whether a key is in a set or map. However, sometimes you just want to know whether the key is absent (for example, to avoid accessing a database, external service, or web page) and can accept false positives if the search just says, "the key may be in the set." This kind of &lt;em&gt;probabilistic search&lt;/em&gt; is a cheap pre-filter in front of a more expensive, authoritative access or check.&lt;/p&gt;

&lt;p&gt;For example, you could be building a password checker. You have a database with billions of compromised usernames. Given a specific user, you want to quickly check whether he may be in the set. If you had added all compromised users to a Bloom Filter, you could do a very quick check; if the filter says "Absent", you are 100% sure the account wasn't compromised. On the other hand, if the filter says "May be present", then, and only then, you'd search thefindfind extensive database or call an external service to (far more slowly) do the definitive test. Using the filter can speed up many searches, requiring a slow check only for a few.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fda1q4l3b5hsg61a7veyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fda1q4l3b5hsg61a7veyy.png" alt="Figure 11½ -1: Algorithm for exact searches with&amp;nbsp;filters" width="781" height="543"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 11½ -1:&lt;/strong&gt; Algorithm for exact searches with&amp;nbsp;filters&lt;/p&gt;

&lt;p&gt;A full dictionary for exact answers requires more space, and probabilistic searches reduce that significantly, at the cost of occasionally saying "maybe" when the true answer is "no". When we have a large key set and tight memory or latency requirements, and we can tolerate a low, controllable rate of unnecessary extra work, we can accept these false positives. And remember, negative answers will always be correct and final.&lt;/p&gt;

&lt;h2&gt;
  
  
  ADT for&amp;nbsp;Filters
&lt;/h2&gt;

&lt;p&gt;A filter's behavior is characterized by a parameter with no analog in Dictionary ADTs: the &lt;em&gt;false-positive&lt;/em&gt; rate, usually written ε. This number actually quantifies how often you may get false-positive answers. It's expressed as a probability: ε=0.01 means roughly a 1% chance that a &lt;code&gt;find&lt;/code&gt; operation will incorrectly return &lt;code&gt;true&lt;/code&gt;. This isn't a bug or an implementation quirk; it's a value you consider when deciding which structure to use and how to size it.&lt;/p&gt;

&lt;p&gt;Also, as with other structures such as hash tables (see the &lt;em&gt;Hashing&lt;/em&gt; section in chapter 11) you must specify how many values (&lt;em&gt;n&lt;/em&gt;) you expect to store. Given &lt;em&gt;n&lt;/em&gt; and ε, we can calculate the right size for specific filter implementations. The following table shows the Filter ADT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnx8axlykw0auy3nj6hd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnx8axlykw0auy3nj6hd9.png" alt="Table 11½-1: Operations on Filters" width="694" height="357"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Table 11½-1: Operations on Filters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are several interesting comments here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the &lt;code&gt;add&lt;/code&gt; operation, we do not worry if a value is repeatedly added to the filter. However, in some implementations this may cause issues later, as we'll see.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;remove&lt;/code&gt; operation may not be required or allowed. Some structures cannot support it at all; some can. An important issue here is that removing a value that wasn't in the filter may cause false negatives, which breaks the ADT contract!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More extreme still, some filters trade away both &lt;code&gt;add&lt;/code&gt; and remove operations after initial construction in exchange for better space and speed; in these cases, the &lt;code&gt;create&lt;/code&gt; operation takes the whole set of values used to build the filter instead of the &lt;em&gt;n&lt;/em&gt; parameter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, we could support an &lt;code&gt;empty?&lt;/code&gt; operation (given a filter, determine whether it is empty) and a &lt;code&gt;size&lt;/code&gt; operation (given a filter, determine how many values it includes) but I'll leave those implementations up to you.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparing Dictionaries and&amp;nbsp;Filters
&lt;/h2&gt;

&lt;p&gt;To summarize, Filters differ from Dictionaries in three important ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compactness&lt;/strong&gt; - Filter implementations use substantially less space than Dictionary implementations, typically a small constant number of bits per element.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-sided error&lt;/strong&gt; - The find operation never produces a false negative. However, it may produce false positives at a rate bounded by ε.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tunable accuracy&lt;/strong&gt; - ε is a construction parameter and can be driven down (at the cost of more space) as far as the application needs. Dictionaries are always 100% accurate; filters, by definition, are not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We've covered all the considerations to keep in mind when planning filters; now let's move on to actual implementations.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bye for now! In &lt;a href="https://dev.to/fkereki/filters-and-probabilistic-searching-part-2-bloom-filters-the-theory-441k"&gt;the next article in the series&lt;/a&gt;, we will start with Bloom Filters, the first implementation we'll be considering.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
