<?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: Saurabh</title>
    <description>The latest articles on DEV Community by Saurabh (@saurabh_pokhriyal).</description>
    <link>https://dev.to/saurabh_pokhriyal</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%2F4182523%2F69d5bc4e-6cd7-4003-a5b3-ea2f45c0294b.png</url>
      <title>DEV Community: Saurabh</title>
      <link>https://dev.to/saurabh_pokhriyal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saurabh_pokhriyal"/>
    <language>en</language>
    <item>
      <title>Bloom Filters Explained: A Simple Guide With Real-World Examples and JavaScript</title>
      <dc:creator>Saurabh</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:09:45 +0000</pubDate>
      <link>https://dev.to/saurabh_pokhriyal/bloom-filters-explained-a-simple-guide-with-real-world-examples-and-javascript-5hea</link>
      <guid>https://dev.to/saurabh_pokhriyal/bloom-filters-explained-a-simple-guide-with-real-world-examples-and-javascript-5hea</guid>
      <description>&lt;p&gt;Have you ever wondered how a system can check whether something might exist among millions of records without storing every single record in its memory?&lt;/p&gt;

&lt;p&gt;Imagine you are building an application with 10 million registered users.&lt;/p&gt;

&lt;p&gt;Whenever somebody enters an email address, your application needs to check whether that email has already been registered.&lt;/p&gt;

&lt;p&gt;The straightforward solution is to query your database. But imagine that your application performs millions of such checks every day. Could you avoid some unnecessary database queries?&lt;/p&gt;

&lt;p&gt;One interesting solution is a &lt;strong&gt;Bloom Filter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The name might sound complicated, but the basic idea is surprisingly simple. Let's understand it from scratch, explore where it is useful, and build a small working implementation in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. First, understand the problem
&lt;/h2&gt;

&lt;p&gt;Suppose you maintain a website with the following registered users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;alice@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bob@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;charlie@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;david@example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a new visitor enters &lt;code&gt;alice@example.com&lt;/code&gt;, your application needs to determine whether the email already exists.&lt;/p&gt;

&lt;p&gt;You could store all registered emails in a database or an in-memory data structure such as a Set.&lt;/p&gt;

&lt;p&gt;That works well. In many applications, an ordinary database or Set is exactly what you should use.&lt;/p&gt;

&lt;p&gt;But what happens when your data grows significantly and checking every possible record becomes expensive?&lt;/p&gt;

&lt;p&gt;For example, finding that a particular item does not exist in a large database may still require accessing storage or checking an index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if we had a much smaller structure whose main job was to quickly tell us when an item definitely does not exist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the problem a Bloom Filter helps solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What exactly is a Bloom Filter?
&lt;/h2&gt;

&lt;p&gt;A Bloom Filter is a space-efficient data structure that answers a simple question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have I probably seen this item before?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It does not store the original items like a normal Set. Instead, it maintains a collection of bits and uses hash functions to mark positions associated with each item.&lt;/p&gt;

&lt;p&gt;Its answers have two possible meanings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definitely not present:&lt;/strong&gt; The item was not added to this filter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Possibly present:&lt;/strong&gt; The item may have been added, but we cannot be certain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the word &lt;em&gt;possibly&lt;/em&gt;. It is important.&lt;/p&gt;

&lt;p&gt;A Bloom Filter can occasionally say that an item might exist even when it was never added. This is called a &lt;strong&gt;false positive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The standard Bloom Filter is designed to avoid false negatives, provided items are inserted and checked correctly using the same filter.&lt;/p&gt;

&lt;p&gt;Let's see why.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Imagine a board full of tiny lights
&lt;/h2&gt;

&lt;p&gt;Forget the code for a moment.&lt;/p&gt;

&lt;p&gt;Imagine a board with 10 small lights. Initially, every light is switched off.&lt;/p&gt;

&lt;p&gt;We represent an off light with &lt;code&gt;0&lt;/code&gt; and an on light with &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our empty board looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0 0 0 0 0 0 0 0 0 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now imagine every email address is associated with three positions on this board.&lt;/p&gt;

&lt;p&gt;For example, when we add Alice's email, our hash functions point to positions 1, 4, and 7.&lt;/p&gt;

&lt;p&gt;We switch those positions on.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0 1 0 0 1 0 0 1 0 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's it. The board now contains information about Alice, but it does not contain her email address.&lt;/p&gt;

&lt;p&gt;Next, we add Bob's email. Suppose it maps to positions 2, 4, and 8.&lt;/p&gt;

&lt;p&gt;After adding Bob, the board becomes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0 1 1 0 1 0 0 1 1 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice something interesting: position 4 was already on. Adding Bob did not require any additional information to be stored there.&lt;/p&gt;

&lt;p&gt;Now let's check another email address, Eve.&lt;/p&gt;

&lt;p&gt;Suppose Eve maps to positions 1, 4, and 8. All three positions are already on.&lt;/p&gt;

&lt;p&gt;Our Bloom Filter says that Eve's email &lt;em&gt;might&lt;/em&gt; exist.&lt;/p&gt;

&lt;p&gt;But we never added Eve!&lt;/p&gt;

&lt;p&gt;Alice and Bob happened to switch on all the positions associated with Eve. Our tiny board cannot tell which items originally switched on each light.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the key idea behind a Bloom Filter: it remembers which positions have been marked, not the original items that marked them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a real implementation, the positions are calculated using hash functions rather than chosen manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What are hash functions, and why do we need them?
&lt;/h2&gt;

&lt;p&gt;A hash function takes an input and turns it into a number.&lt;/p&gt;

&lt;p&gt;For example, a hash function might turn &lt;code&gt;alice@example.com&lt;/code&gt; into &lt;code&gt;471&lt;/code&gt;. A different hash function might turn the same email into &lt;code&gt;1204&lt;/code&gt;, and a third into &lt;code&gt;987&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But our board only has 10 lights, numbered 0 to 9, so we need a valid position. The fix is to take the remainder after dividing by the board size (the &lt;code&gt;%&lt;/code&gt; operator):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;471 % 10 = 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1204 % 10 = 4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;987 % 10 = 7&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's how Alice ended up at positions 1, 4 and 7 in the previous section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When adding an item:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass the item through each hash function.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;% size&lt;/code&gt; to turn each result into a position in the bit array.&lt;/li&gt;
&lt;li&gt;Change those bits from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;When checking an item:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass the item through the same hash functions.&lt;/li&gt;
&lt;li&gt;Calculate the same positions.&lt;/li&gt;
&lt;li&gt;Check whether every one of those bits is on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If even one bit is off, the item definitely was not added. If every bit is on, the item might have been added, or other items might have switched those bits on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One rule matters a lot: hash functions must be deterministic.&lt;/strong&gt; The same input must always give the same positions. If "alice" hashed to 1, 4, 7 when added but to 2, 5, 9 when checked, you would look at the wrong lights and wrongly say "not present". The filter would then have false negatives, which a Bloom filter must never have.&lt;/p&gt;

&lt;p&gt;A good hash function also spreads its results evenly across the board. If it favors certain positions, those lights fill up quickly and false positives become more common.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What is a false positive?
&lt;/h2&gt;

&lt;p&gt;Let's revisit our example.&lt;/p&gt;

&lt;p&gt;Alice and Bob switched on several lights. Later, Eve's email mapped to positions that were already switched on.&lt;/p&gt;

&lt;p&gt;The filter returned a positive result even though Eve was never added.&lt;/p&gt;

&lt;p&gt;This is a false positive.&lt;/p&gt;

&lt;p&gt;It is not a programming bug. It is an expected trade-off of a standard Bloom Filter.&lt;/p&gt;

&lt;p&gt;The important thing is that the probability can be controlled.&lt;/p&gt;

&lt;p&gt;Several factors affect the likelihood of a false positive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Size of the filter:&lt;/strong&gt; More bits generally provide more space to represent the same number of items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of items:&lt;/strong&gt; Adding more items makes more bits switch on, increasing the chance of overlap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of hash functions (k):&lt;/strong&gt; Too few, and different items overlap easily. Too many, and each item switches on so many lights that the board fills up quickly. There is a sweet spot in between.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a filter becomes too full, false positives become more likely. That is why choosing an appropriate capacity and target false-positive rate matters.&lt;/p&gt;

&lt;p&gt;For example, a target false-positive rate of 1% means that approximately 1% of queries for items that were not added may return a positive result under the design's expected operating conditions. It does not mean that 1% of all positive answers are necessarily wrong.&lt;/p&gt;

&lt;p&gt;That distinction matters when interpreting results.&lt;/p&gt;

&lt;h3&gt;
  
  
  How big should the filter be?
&lt;/h3&gt;

&lt;p&gt;You don't have to guess. If you know how many items you expect (&lt;code&gt;n&lt;/code&gt;) and the false-positive rate you can tolerate (&lt;code&gt;p&lt;/code&gt;), two formulas give the best size and number of hash functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bits needed: &lt;code&gt;m = -(n × ln p) / (ln 2)²&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hash functions: &lt;code&gt;k = (m / n) × ln 2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some common targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target false-positive rate&lt;/th&gt;
&lt;th&gt;Bits per item&lt;/th&gt;
&lt;th&gt;Hash functions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;about 4.8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;td&gt;about 9.6&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;td&gt;about 14.4&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So a rough rule is &lt;strong&gt;about 10 bits per item and 7 hash functions for a 1% false-positive rate&lt;/strong&gt;. For 1 million emails, that works out to roughly 9.6 million bits, which is about &lt;strong&gt;1.2 MB&lt;/strong&gt;. A Set holding a million email strings would need far more.&lt;/p&gt;

&lt;p&gt;The same calculation in 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;function&lt;/span&gt; &lt;span class="nf"&gt;optimalSize&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;p&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;m&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;p&lt;/span&gt;&lt;span class="p"&gt;))&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="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&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;k&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;m&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="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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&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="nf"&gt;optimalSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// { m: 9585059, k: 7 }&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;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;optimalSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&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;filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BloomFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// the class we build in the next section&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why the &lt;code&gt;BloomFilter(1000, 3)&lt;/code&gt; we use below is only a toy. Real filters should be sized for the data they will hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Let's build a Bloom Filter in JavaScript
&lt;/h2&gt;

&lt;p&gt;Theory is useful, but let's put it into practice.&lt;/p&gt;

&lt;p&gt;We'll create a small educational implementation using JavaScript. It will accept items, mark positions in a bit array, and tell us whether an item might be present.&lt;/p&gt;

&lt;p&gt;The example uses a basic, non-cryptographic hash function to keep the implementation understandable. It is for learning purposes, not a production-ready implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a hash function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashString&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;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;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;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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;hash&lt;/span&gt; &lt;span class="o"&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;charCodeAt&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;hash&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;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16777619&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;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function converts a string into a 32-bit unsigned integer.&lt;/p&gt;

&lt;p&gt;We use two different seeds to derive multiple positions for each item. The code uses a technique called double hashing, which generates several positions from two hash values.&lt;/p&gt;

&lt;p&gt;You do not need to memorize the hashing details to understand how the Bloom Filter works. The important part is that the same input consistently produces the same positions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Implement the Bloom Filter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BloomFilter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hashCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Eight bits are stored in each byte.&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&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="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getIndexes&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashString&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;2166136261&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;secondHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hashString&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="mh"&gt;0x9e3779b9&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="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="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashCount&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;firstHash&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;secondHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;this&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;span class="nf"&gt;setBit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&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;byteIndex&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;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&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;bitPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;byteIndex&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="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;bitPosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;isBitSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&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;byteIndex&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;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&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;bitPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;8&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;byteIndex&lt;/span&gt;&lt;span class="p"&gt;]&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;bitPosition&lt;/span&gt;&lt;span class="p"&gt;))&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;add&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;index&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIndexes&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&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="nf"&gt;mightContain&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;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIndexes&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBitSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&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;Let's break down the important parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;constructor()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Creates a filter with 1,000 available bit positions by default and uses three hash-derived positions per item.&lt;/p&gt;

&lt;p&gt;We use a &lt;code&gt;Uint8Array&lt;/code&gt; to store eight bits per byte. This allows the example to use an actual packed bit array rather than reserving an entire byte for every position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;getIndexes()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Calculates the positions associated with an item. The same item produces the same positions as long as the filter parameters and hash implementation remain unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;add()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Marks every calculated position as &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mightContain()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Checks every position. If any required bit is &lt;code&gt;0&lt;/code&gt;, it returns &lt;code&gt;false&lt;/code&gt;. Otherwise, it returns &lt;code&gt;true&lt;/code&gt;, meaning the item might exist.&lt;/p&gt;

&lt;p&gt;The method is deliberately called &lt;code&gt;mightContain()&lt;/code&gt; rather than &lt;code&gt;contains()&lt;/code&gt;. A Bloom Filter cannot guarantee that a positive result is correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Try it out
&lt;/h3&gt;

&lt;p&gt;Let's add a registered email address and check a few values.&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;filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BloomFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&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;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mightContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&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;console&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;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mightContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob@example.com&lt;/span&gt;&lt;span class="dl"&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;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true
false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter returns &lt;code&gt;true&lt;/code&gt; for Alice because we added her email.&lt;/p&gt;

&lt;p&gt;It returns &lt;code&gt;false&lt;/code&gt; for Bob because at least one of Bob's required positions is still off.&lt;/p&gt;

&lt;p&gt;With this filter's size and current contents, Bob's check returns &lt;code&gt;false&lt;/code&gt;. However, another absent email could return &lt;code&gt;true&lt;/code&gt; if all its positions happened to be switched on.&lt;/p&gt;

&lt;p&gt;That is the false-positive trade-off in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: See a false positive happen
&lt;/h3&gt;

&lt;p&gt;So far we've only seen the filter behave well. Let's break it on purpose by giving it far too little space: 20 emails squeezed into just 64 bits.&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;tiny&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BloomFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add 20 registered users&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;i&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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;tiny&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`user&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="s2"&gt;@example.com`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Added items are always found&lt;/span&gt;
&lt;span class="nx"&gt;console&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;tiny&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mightContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user7@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="c1"&gt;// Now check 200 emails we never added&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;falsePositives&lt;/span&gt; &lt;span class="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`user&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="s2"&gt;@example.com`&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;tiny&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mightContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;falsePositives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&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="s2"&gt;`False positives: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;falsePositives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; out of 200`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&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;falsePositives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true
False positives: 60 out of 200
[ 'user25@example.com', 'user26@example.com', 'user30@example.com' ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at what happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;user7@example.com&lt;/code&gt; was found, as it always will be. &lt;strong&gt;There are no false negatives.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;60 of the 200 emails we never added were reported as "might exist". With 37 of the 64 bits switched on, a random absent email has a good chance of landing entirely on lights that other users already switched on.&lt;/li&gt;
&lt;li&gt;By the sizing rule from section 5, 20 items at a 1% target need about 192 bits. We gave the filter 64, so it is overcrowded and the false-positive rate is high.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try changing &lt;code&gt;64&lt;/code&gt; to &lt;code&gt;200&lt;/code&gt; and run it again. The false positives should drop sharply. That is the memory-versus-accuracy trade-off in action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One important limitation:&lt;/strong&gt; This is a teaching implementation, not a recommendation to write your own production hashing algorithm. Real systems should use a tested Bloom Filter library with appropriate sizing, hash behavior, concurrency handling, and operational monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A real-world example: Checking whether a username exists
&lt;/h2&gt;

&lt;p&gt;Imagine you're building a registration service that handles millions of username availability checks.&lt;/p&gt;

&lt;p&gt;A simplified request flow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User enters a username
          |
          v
   Check Bloom Filter
          |
     +----+----+
     |         |
   Absent    Possibly
     |         |
     v         v
 Continue    Check database
 registration   |
                v
        Is username registered?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's consider both cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case A: The Bloom Filter says the username is definitely absent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application can skip a preliminary database lookup whose only purpose was to check whether that username existed.&lt;/p&gt;

&lt;p&gt;It can continue with the normal registration process.&lt;/p&gt;

&lt;p&gt;The database must still enforce a unique constraint on the username. Otherwise, two users could try registering the same username at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case B: The Bloom Filter says the username might exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application queries the database to confirm the result.&lt;/p&gt;

&lt;p&gt;If the username exists, it can tell the user that the name is unavailable.&lt;/p&gt;

&lt;p&gt;If the database says the name is free, the application can continue registration. The Bloom Filter may simply have produced a false positive.&lt;/p&gt;

&lt;p&gt;This approach can help reduce unnecessary database lookups when many queried usernames are absent.&lt;/p&gt;

&lt;p&gt;There is an important engineering detail here: the filter must stay consistent with the actual registered-user data. Adding items too early or failing to update the filter after successful registrations can undermine the reliability of the optimization.&lt;/p&gt;

&lt;p&gt;A Bloom Filter is an optimization, not a replacement for database correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Where else are Bloom Filters useful?
&lt;/h2&gt;

&lt;p&gt;The username example is only one possible application.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Databases and storage engines
&lt;/h3&gt;

&lt;p&gt;Databases sometimes need to check whether a key may be present in a particular on-disk data structure.&lt;/p&gt;

&lt;p&gt;Checking storage can be more expensive than checking information already available in memory.&lt;/p&gt;

&lt;p&gt;A Bloom Filter can help the database avoid some unnecessary storage reads when it determines that a key is definitely absent.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Web crawlers
&lt;/h3&gt;

&lt;p&gt;Imagine a web crawler exploring millions of pages.&lt;/p&gt;

&lt;p&gt;Before processing a URL, it needs to determine whether that URL may have already been visited.&lt;/p&gt;

&lt;p&gt;A Bloom Filter can provide a compact preliminary check. A positive result might require further verification, depending on the crawler's requirements.&lt;/p&gt;

&lt;p&gt;This is useful when memory is limited, but a false positive could cause a page to be skipped incorrectly. That trade-off must be considered.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Recommendation and advertising systems
&lt;/h3&gt;

&lt;p&gt;A service might want to avoid repeatedly recommending the same item or displaying the same advertisement.&lt;/p&gt;

&lt;p&gt;A Bloom Filter can help identify items that may already have been seen.&lt;/p&gt;

&lt;p&gt;However, if showing an item twice is unacceptable, the application needs an exact verification mechanism rather than relying solely on the filter.&lt;/p&gt;

&lt;h3&gt;
  
  
  D. Large-scale data processing
&lt;/h3&gt;

&lt;p&gt;When processing huge datasets, a system may want to avoid performing an expensive lookup for every item.&lt;/p&gt;

&lt;p&gt;A Bloom Filter can serve as a compact preliminary check, reducing unnecessary work when a large proportion of the queried items are absent.&lt;/p&gt;

&lt;p&gt;The broader pattern is the same: &lt;strong&gt;use a small structure to avoid expensive work whenever it can confidently rule an item out.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Bloom Filter vs. Set: Which should you use?
&lt;/h2&gt;

&lt;p&gt;A Bloom Filter is not universally better than a Set. Each solves a different problem.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;JavaScript &lt;code&gt;Set&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Bloom Filter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stores original items&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage&lt;/td&gt;
&lt;td&gt;Depends on stored items and implementation&lt;/td&gt;
&lt;td&gt;Usually much smaller for large collections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absent-item check&lt;/td&gt;
&lt;td&gt;Exact&lt;/td&gt;
&lt;td&gt;Exact negative result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Present-item check&lt;/td&gt;
&lt;td&gt;Exact&lt;/td&gt;
&lt;td&gt;May be a false positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete an item&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Not supported by a standard Bloom Filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best suited for&lt;/td&gt;
&lt;td&gt;When exact membership matters&lt;/td&gt;
&lt;td&gt;When memory savings and fast preliminary checks matter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a small application storing a few hundred usernames, a Set may be simpler and entirely sufficient.&lt;/p&gt;

&lt;p&gt;For a large system where memory usage matters and checking absent items is expensive, a Bloom Filter may be worth considering.&lt;/p&gt;

&lt;p&gt;The right choice depends on the requirements, not simply on which data structure sounds more advanced.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Important limitations to remember
&lt;/h2&gt;

&lt;p&gt;Bloom Filters are useful, but they are not magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They cannot recover the original data.&lt;/strong&gt; The bit array does not tell you which email addresses, URLs, or IDs were inserted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False positives are possible.&lt;/strong&gt; A positive result needs verification whenever correctness depends on exact membership.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard Bloom Filters do not support deletion.&lt;/strong&gt; Setting a bit back to zero could accidentally remove information needed by other items. Variants such as counting Bloom Filters can support deletion with additional complexity and storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing matters.&lt;/strong&gt; If you insert substantially more items than the filter was designed for, its false-positive rate can rise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are not a security mechanism.&lt;/strong&gt; The basic implementation presented here is not designed to protect sensitive data, and a Bloom Filter should not replace appropriate security checks.&lt;/p&gt;

&lt;p&gt;Understanding these limitations is just as important as understanding how the data structure works.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. The takeaway
&lt;/h2&gt;

&lt;p&gt;A Bloom Filter is a great example of a practical engineering trade-off.&lt;/p&gt;

&lt;p&gt;Instead of storing every original item, it stores a compact representation of information about those items.&lt;/p&gt;

&lt;p&gt;In return for saving memory, it accepts uncertainty when the answer is positive.&lt;/p&gt;

&lt;p&gt;Remember these three points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If a standard Bloom Filter says an item is absent, the item was not added, assuming the filter has been used correctly.&lt;/li&gt;
&lt;li&gt;If it says an item might be present, the result is not guaranteed to be correct.&lt;/li&gt;
&lt;li&gt;It is most useful when ruling out absent items can save significant memory or expensive work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next time you encounter a system that handles millions of records, ask yourself: &lt;em&gt;Do we really need to perform an exact lookup every time, or can we cheaply rule out most absent items first?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is where a Bloom Filter might be worth exploring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redis.io/docs/latest/develop/data-types/probabilistic/bloom-filter/" rel="noopener noreferrer"&gt;Redis documentation: Bloom Filters&lt;/a&gt; — Practical examples, sizing, and false-positive rates.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doi.org/10.1145/362686.362692" rel="noopener noreferrer"&gt;Bloom's original paper: Space/Time Trade-offs in Hash Coding with Allowable Errors&lt;/a&gt; — The foundational paper behind the data structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Thanks for reading! If you found this explanation helpful, share your thoughts or examples of where you have seen memory-efficient data structures used in real applications.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>beginners</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Beyond Unit Tests: How to Test and Evaluate LLM Applications</title>
      <dc:creator>Saurabh</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:12:21 +0000</pubDate>
      <link>https://dev.to/saurabh_pokhriyal/beyond-unit-tests-how-to-test-and-evaluate-llm-applications-4104</link>
      <guid>https://dev.to/saurabh_pokhriyal/beyond-unit-tests-how-to-test-and-evaluate-llm-applications-4104</guid>
      <description>&lt;p&gt;Imagine deploying an AI-powered chatbot. Your API returns &lt;code&gt;200 OK&lt;/code&gt;, all your unit tests pass, and the application responds within an acceptable time.&lt;/p&gt;

&lt;p&gt;But there is one problem: &lt;strong&gt;the answer is wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The chatbot confidently provides an incorrect refund policy, invents a step that does not exist in the documentation, or answers a question that it does not have enough information to answer.&lt;/p&gt;

&lt;p&gt;From a traditional software testing perspective, everything looks fine. From the user's perspective, the application has failed.&lt;/p&gt;

&lt;p&gt;This is one of the biggest challenges when building applications powered by Large Language Models (LLMs).&lt;/p&gt;

&lt;p&gt;Unlike traditional functions, LLMs can generate different responses to the same input. A response can be grammatically correct and well-structured while still being factually incorrect.&lt;/p&gt;

&lt;p&gt;So, how do we test these applications effectively?&lt;/p&gt;

&lt;p&gt;In this article, we'll explore practical approaches to LLM evaluation, hallucination testing, regression testing, and production monitoring, with examples relevant to JavaScript and backend engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why Traditional Unit Tests Are Not Enough
&lt;/h2&gt;

&lt;p&gt;Consider a traditional function:&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;function&lt;/span&gt; &lt;span class="nf"&gt;calculateDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discountPercentage&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;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;discountPercentage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&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="nf"&gt;calculateDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 900&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a given input, we can define an expected output and assert that the result matches 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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculateDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a straightforward and deterministic test.&lt;/p&gt;

&lt;p&gt;Now consider an AI assistant that answers questions using internal company documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"What is the refund processing time?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected answer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Refunds are processed within five business days."&lt;/p&gt;

&lt;p&gt;The LLM might answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Refunds are processed within five business days."&lt;/li&gt;
&lt;li&gt;"You should receive your refund within five working days."&lt;/li&gt;
&lt;li&gt;"The refund process typically takes about a week."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These answers may not be equivalent in every context. The third answer, for example, is less precise and might introduce ambiguity.&lt;/p&gt;

&lt;p&gt;An exact string comparison cannot reliably tell us whether the response preserves the intended meaning.&lt;/p&gt;

&lt;p&gt;This is why AI application testing needs more than traditional unit tests.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional testing&lt;/th&gt;
&lt;th&gt;AI evaluation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Does the function return the expected value?&lt;/td&gt;
&lt;td&gt;Does the answer communicate the correct meaning?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the API return a successful status?&lt;/td&gt;
&lt;td&gt;Does the response actually solve the user's problem?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the output match a fixed structure?&lt;/td&gt;
&lt;td&gt;Is the response relevant, accurate, and supported by evidence?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the application handle errors?&lt;/td&gt;
&lt;td&gt;Does it also behave safely when the answer is unknown?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The solution is not to replace unit tests. It's to combine them with additional evaluation techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Should We Measure in an AI Application?
&lt;/h2&gt;

&lt;p&gt;Before evaluating an AI system, we need to define what a good response looks like.&lt;/p&gt;

&lt;p&gt;There is no single metric that can measure every aspect of AI quality. Different applications require different evaluation criteria.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Correctness
&lt;/h3&gt;

&lt;p&gt;Does the response provide the correct answer?&lt;/p&gt;

&lt;p&gt;For a customer support assistant, correctness might mean that refund periods, eligibility conditions, and other policy details match the approved source.&lt;/p&gt;

&lt;p&gt;Possible approaches include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comparing answers against reference answers.&lt;/li&gt;
&lt;li&gt;Checking required facts or claims.&lt;/li&gt;
&lt;li&gt;Having domain experts review a representative sample.&lt;/li&gt;
&lt;li&gt;Using an automated evaluator with clearly defined criteria.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  B. Relevance
&lt;/h3&gt;

&lt;p&gt;Does the response answer the question that was asked?&lt;/p&gt;

&lt;p&gt;For example, if someone asks how to reset a password, a detailed explanation of account registration is not a relevant answer, even if all the information is accurate.&lt;/p&gt;

&lt;p&gt;Relevance matters when building documentation assistants, support chatbots, and search applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Faithfulness and hallucinations
&lt;/h3&gt;

&lt;p&gt;For applications that retrieve documents before generating an answer, faithfulness means that the answer's claims are supported by the retrieved context.&lt;/p&gt;

&lt;p&gt;Suppose the source document states:&lt;/p&gt;

&lt;p&gt;"Refunds are processed within five business days."&lt;/p&gt;

&lt;p&gt;The assistant answers:&lt;/p&gt;

&lt;p&gt;"Refunds are processed within five business days, and a confirmation email is always sent within one hour."&lt;/p&gt;

&lt;p&gt;If the source says nothing about a confirmation email, the additional claim is unsupported.&lt;/p&gt;

&lt;p&gt;This is a useful way to test hallucinations in Retrieval-Augmented Generation (RAG) applications.&lt;/p&gt;

&lt;p&gt;However, an answer that is faithful to a document is not automatically correct in the real world. The source itself could be outdated or incorrect, so source quality matters too.&lt;/p&gt;

&lt;h3&gt;
  
  
  D. Completeness
&lt;/h3&gt;

&lt;p&gt;Does the response cover all the important parts of the question?&lt;/p&gt;

&lt;p&gt;If a user asks for three steps and the assistant provides only two, the answer may be accurate but incomplete.&lt;/p&gt;

&lt;h3&gt;
  
  
  E. Safety
&lt;/h3&gt;

&lt;p&gt;Does the application avoid unsafe or unauthorized behavior?&lt;/p&gt;

&lt;p&gt;Examples include refusing to reveal another customer's private information, following access restrictions, and resisting attempts to bypass system instructions.&lt;/p&gt;

&lt;p&gt;Safety tests should cover both ordinary usage and adversarial inputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The important lesson:&lt;/strong&gt; Define your evaluation criteria before choosing the metrics. A high score on one metric does not guarantee that the application is good overall.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Building a Small Evaluation Dataset
&lt;/h2&gt;

&lt;p&gt;One of the most practical ways to test an LLM application is to maintain a fixed dataset of representative questions and expected behavior.&lt;/p&gt;

&lt;p&gt;This dataset can be treated as an evaluation suite and run whenever you change a prompt, model, retrieval configuration, or important application component.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test scenario&lt;/th&gt;
&lt;th&gt;Expected behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ask for the refund processing time&lt;/td&gt;
&lt;td&gt;Return the documented time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ask about an unsupported policy&lt;/td&gt;
&lt;td&gt;Acknowledge that the information is unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request a short summary&lt;/td&gt;
&lt;td&gt;Preserve the key facts without adding claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ask a question unrelated to the documentation&lt;/td&gt;
&lt;td&gt;Avoid inventing an answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request another customer's private data&lt;/td&gt;
&lt;td&gt;Do not disclose unauthorized information&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A basic dataset might look like this:&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;evaluationCases&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Refund processing time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How long does a refund take?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requiredPhrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;five business days&lt;/span&gt;&lt;span class="dl"&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown information&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is the refund policy for a product not covered by the documentation?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;uncertaintyPhrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not specified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cannot confirm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;don't have enough information&lt;/span&gt;&lt;span class="dl"&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 required phrases and accepted uncertainty phrases are illustrative. They should be aligned with the actual policy and the response conventions used by your application.&lt;/p&gt;

&lt;p&gt;As the application grows, include more examples covering different user intents, edge cases, and failure modes. Keep the dataset representative rather than filling it only with easy questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Writing AI Regression Tests in JavaScript
&lt;/h2&gt;

&lt;p&gt;Let's turn the dataset into a simple automated test suite.&lt;/p&gt;

&lt;p&gt;Assume that our application exposes an asynchronous &lt;code&gt;answerQuestion()&lt;/code&gt; function that returns an object containing an &lt;code&gt;answer&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;We can use Node.js's built-in test runner, avoiding the need for an additional test framework.&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;import&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;answerQuestion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../src/assistant.js&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;evaluationCases&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Refund processing time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How long does a refund take?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requiredPhrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;five business days&lt;/span&gt;&lt;span class="dl"&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown information&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is the refund policy for an unsupported product?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;uncertaintyPhrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not specified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cannot confirm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;don't have enough information&lt;/span&gt;&lt;span class="dl"&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="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;scenario&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;evaluationCases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;answerQuestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requiredPhrases&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;phrase&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requiredPhrases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phrase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
          &lt;span class="s2"&gt;`Expected answer to contain: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;phrase&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="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;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uncertaintyPhrases&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;acknowledgesUncertainty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uncertaintyPhrases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;phrase&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;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phrase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;acknowledgesUncertainty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expected the assistant to acknowledge missing information&lt;/span&gt;&lt;span class="dl"&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;Run the tests with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple baseline, not a complete semantic evaluator.&lt;/p&gt;

&lt;p&gt;The code checks whether the response contains expected wording. That can catch obvious regressions, but a correct answer that uses different wording might fail. Conversely, a response could include the required phrase and still contain incorrect additional claims.&lt;/p&gt;

&lt;p&gt;For production systems, combine these checks with claim-level verification, semantic evaluation, and human review where appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when the prompt changes?
&lt;/h3&gt;

&lt;p&gt;Imagine that the team modifies the system prompt to make answers shorter.&lt;/p&gt;

&lt;p&gt;The change may improve response time and reduce token usage, but it could also remove important policy details.&lt;/p&gt;

&lt;p&gt;Running the evaluation dataset again helps determine whether the change improved the system overall or introduced regressions.&lt;/p&gt;

&lt;p&gt;This is the central benefit of an AI regression suite: changes are evaluated against a repeatable set of expectations instead of relying only on a few manual experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Using an LLM as an Evaluator
&lt;/h2&gt;

&lt;p&gt;Exact phrase matching is useful for specific requirements, but many answers need semantic evaluation.&lt;/p&gt;

&lt;p&gt;For example, these two answers communicate approximately the same information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"The request will be processed within five business days."&lt;/li&gt;
&lt;li&gt;"Please allow five working days for processing."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple string comparison may mark them as different even when both are acceptable.&lt;/p&gt;

&lt;p&gt;One approach is to use a separate LLM to evaluate the generated response. This technique is commonly known as LLM-as-a-judge.&lt;/p&gt;

&lt;p&gt;The evaluator can be given:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user's question.&lt;/li&gt;
&lt;li&gt;The relevant source context.&lt;/li&gt;
&lt;li&gt;The generated answer.&lt;/li&gt;
&lt;li&gt;A rubric describing how the answer should be assessed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, we might ask the evaluator to score each dimension from 1 to 5:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Evaluation question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Relevance&lt;/td&gt;
&lt;td&gt;Does the answer address the user's question?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faithfulness&lt;/td&gt;
&lt;td&gt;Are the answer's claims supported by the supplied context?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;Does it cover the required information?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clarity&lt;/td&gt;
&lt;td&gt;Is it understandable and unambiguous?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A simple evaluation instruction could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are evaluating an AI-generated answer.

Question:
{question}

Source context:
{context}

Generated answer:
{answer}

Evaluate relevance, faithfulness, and completeness
on a scale from 1 to 5.

Use the supplied source context to assess faithfulness.
Do not reward an answer merely because it sounds confident.
Identify any unsupported claims and explain the reason
for each score.

Return structured JSON containing the scores,
unsupported claims, and a short explanation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evaluator should follow a consistent rubric and produce structured output that can be stored and compared.&lt;/p&gt;

&lt;p&gt;However, an LLM evaluator is not a perfect source of truth. It can overlook errors, prefer fluent answers, or make its own incorrect judgments.&lt;/p&gt;

&lt;p&gt;To improve reliability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate the evaluator's results against human-reviewed examples.&lt;/li&gt;
&lt;li&gt;Keep the evaluation instructions consistent across comparisons.&lt;/li&gt;
&lt;li&gt;Use trusted source material where available.&lt;/li&gt;
&lt;li&gt;Review borderline cases and disagreements.&lt;/li&gt;
&lt;li&gt;Do not rely on one automated score for critical safety decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLM-as-a-judge is best treated as a scalable evaluation aid, not a replacement for ground truth or human judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Measuring Latency, Token Usage, and Cost
&lt;/h2&gt;

&lt;p&gt;Answer quality is only one part of production readiness.&lt;/p&gt;

&lt;p&gt;A response can be accurate but take too long to generate. Another response might be acceptable but consume significantly more tokens than expected.&lt;/p&gt;

&lt;p&gt;For this reason, quality evaluations should also track operational metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;Measure how long a request takes to complete.&lt;/p&gt;

&lt;p&gt;Useful metrics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Median latency (p50).&lt;/li&gt;
&lt;li&gt;p95 latency.&lt;/li&gt;
&lt;li&gt;Timeout rate.&lt;/li&gt;
&lt;li&gt;Error rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The p95 latency tells us the response time at or below which approximately 95% of measured requests complete. It helps identify slow experiences that an average can hide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token usage and cost
&lt;/h3&gt;

&lt;p&gt;When supported by the model provider, collect input and output token counts for every request.&lt;/p&gt;

&lt;p&gt;A simplified cost calculation is:&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;function&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputPricePerMillion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outputPricePerMillion&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;inputPricePerMillion&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;outputPricePerMillion&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 price values should come from the applicable model's current pricing configuration. This calculation is an estimate and may not include other infrastructure or service costs.&lt;/p&gt;

&lt;p&gt;Track cost per request, cost by feature, and cost per successfully completed user task.&lt;/p&gt;

&lt;p&gt;The last measure is particularly useful because reducing the cost of each response does not necessarily improve the system if the answers become less useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality alongside performance
&lt;/h3&gt;

&lt;p&gt;Consider a new model or prompt configuration that reduces average latency by 20% but also increases incorrect answers.&lt;/p&gt;

&lt;p&gt;Looking only at latency would suggest an improvement. Evaluating quality and operational metrics together gives the team a more complete picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Monitoring an AI Application in Production
&lt;/h2&gt;

&lt;p&gt;Offline evaluation helps us test changes before deployment. It cannot predict every real-world input or failure.&lt;/p&gt;

&lt;p&gt;Production monitoring is therefore equally important.&lt;/p&gt;

&lt;p&gt;A useful monitoring strategy includes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical health&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request volume and error rate.&lt;/li&gt;
&lt;li&gt;Model-provider failures and timeouts.&lt;/li&gt;
&lt;li&gt;Median and p95 latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost and usage&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input and output tokens.&lt;/li&gt;
&lt;li&gt;Estimated cost by model and feature.&lt;/li&gt;
&lt;li&gt;Unexpected usage spikes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI quality&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Periodic evaluation of sampled responses.&lt;/li&gt;
&lt;li&gt;Unsupported claims and retrieval failures.&lt;/li&gt;
&lt;li&gt;User feedback and task completion rates.&lt;/li&gt;
&lt;li&gt;Changes in quality after a model or prompt update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a RAG system, it can also be valuable to monitor retrieval quality separately from answer generation. The retriever may return irrelevant documents even when the LLM is following those documents correctly.&lt;/p&gt;

&lt;p&gt;When logging requests, take care to protect customer information. Redact sensitive data, apply appropriate access controls, and define retention rules rather than storing every raw prompt and answer indefinitely.&lt;/p&gt;

&lt;p&gt;The goal is not to capture everything. It is to collect the right signals to detect failures and investigate them safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Putting It All Together: An AI Testing Workflow
&lt;/h2&gt;

&lt;p&gt;A practical workflow for an engineering team could look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define quality criteria.&lt;/strong&gt; Decide what a correct, relevant, safe response means for the feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build an evaluation dataset.&lt;/strong&gt; Include normal scenarios, edge cases, and known failure modes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run traditional tests.&lt;/strong&gt; Verify API contracts, validation, authentication, error handling, and application logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run AI evaluations.&lt;/strong&gt; Check correctness, relevance, faithfulness, and the behaviors that matter to the feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare with the previous version.&lt;/strong&gt; Investigate regressions after prompt, model, or retrieval changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set deployment gates.&lt;/strong&gt; Use thresholds for critical metrics that reflect the application's actual requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor production.&lt;/strong&gt; Track latency, errors, cost, feedback, and sampled quality evaluations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed real failures back into the dataset.&lt;/strong&gt; Turn recurring production problems into regression tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates a continuous improvement loop: evaluation informs deployment, and production observations inform future evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Testing AI applications requires a broader approach than checking whether an API responds successfully.&lt;/p&gt;

&lt;p&gt;Traditional unit and integration tests remain essential, but they do not establish that an LLM's answer is correct, relevant, complete, or safe.&lt;/p&gt;

&lt;p&gt;A stronger strategy combines repeatable evaluation datasets, clearly defined quality criteria, semantic evaluation, regression testing, and production monitoring.&lt;/p&gt;

&lt;p&gt;You do not need a complicated evaluation platform to get started. A small dataset, an automated test runner, a consistent evaluation rubric, and basic latency and cost tracking can already make AI development more disciplined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key takeaway is simple: don't just test whether your AI application works. Test whether it works well enough for the user and the problem it is designed to solve.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://platform.openai.com/docs/guides/evals" rel="noopener noreferrer"&gt;OpenAI Evals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ragas.io/" rel="noopener noreferrer"&gt;Ragas Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.confident-ai.com/" rel="noopener noreferrer"&gt;DeepEval Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.smith.langchain.com/" rel="noopener noreferrer"&gt;LangSmith Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
