<?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: Umang Sinha</title>
    <description>The latest articles on DEV Community by Umang Sinha (@umangsinha12).</description>
    <link>https://dev.to/umangsinha12</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F468034%2F5be9fb90-2148-4b01-87b0-c369cad1979d.png</url>
      <title>DEV Community: Umang Sinha</title>
      <link>https://dev.to/umangsinha12</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/umangsinha12"/>
    <language>en</language>
    <item>
      <title>how spaced repetition actually works: the sm-2 algorithm</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Sun, 08 Mar 2026 07:50:31 +0000</pubDate>
      <link>https://dev.to/umangsinha12/how-spaced-repetition-actually-works-the-sm-2-algorithm-1ge3</link>
      <guid>https://dev.to/umangsinha12/how-spaced-repetition-actually-works-the-sm-2-algorithm-1ge3</guid>
      <description>&lt;p&gt;most people study inefficiently.&lt;/p&gt;

&lt;p&gt;we review things too early - wasting time. or too late - after we've already forgotten them.&lt;/p&gt;

&lt;p&gt;the real question is simple: &lt;strong&gt;when is the optimal moment to review something?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;in 1987, a polish researcher named piotr woźniak proposed a surprisingly simple answer. he built an algorithm that schedules reviews so that you see information &lt;em&gt;just before you forget it&lt;/em&gt;. that algorithm is called &lt;strong&gt;sm-2&lt;/strong&gt;, and variations of it power spaced-repetition tools used by millions of people today, including systems like anki.&lt;/p&gt;

&lt;p&gt;the fascinating part is that the core idea is extremely small. the entire algorithm fits in a few variables and a short formula.&lt;/p&gt;

&lt;p&gt;but to understand why it works, we first need to look at how memory behaves.&lt;/p&gt;

&lt;p&gt;human memory decays quickly. in the late 19th century, psychologist hermann ebbinghaus studied how quickly people forget newly learned information. his experiments produced what we now call the "forgetting curve" - a model showing that memory retention drops rapidly over time unless the information is reinforced.&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.amazonaws.com%2Fuploads%2Farticles%2F4e6q7hrf5uahyi9ralku.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.amazonaws.com%2Fuploads%2Farticles%2F4e6q7hrf5uahyi9ralku.png" alt="the ebbinghaus forgetting curve with spaced repetition&amp;lt;br&amp;gt;
" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;if you learn something today and never revisit it, the probability of remembering it decreases dramatically over the next few days. &lt;br&gt;
this means that reviewing information randomly is inefficient. review too early and you're spending time on something you still remember well. review too late and you've already forgotten it.&lt;/p&gt;

&lt;p&gt;the optimal strategy is to review something &lt;em&gt;right before you forget it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;this idea is the foundation of &lt;strong&gt;spaced repetition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;instead of reviewing information repeatedly in a short period of time, spaced repetition increases the gap between reviews. a typical review schedule might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;day 0 → day 1 → day 3 → day 7 → day 16 → day 35 → day 90&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;each successful recall strengthens the memory, allowing the next review to be pushed further into the future.&lt;/p&gt;

&lt;p&gt;however, there is a problem with using a fixed schedule. not all information has the same difficulty.&lt;/p&gt;

&lt;p&gt;some facts stick instantly. others require repeated effort to remember. a system that treats all information the same will inevitably be inefficient.&lt;/p&gt;

&lt;p&gt;this is where sm-2 becomes interesting.&lt;/p&gt;

&lt;p&gt;instead of scheduling reviews globally, sm-2 assigns a small learning model to every flashcard. each card tracks a few pieces of information about your interaction with it, and the scheduling decisions are made based on that history.&lt;/p&gt;

&lt;p&gt;the algorithm keeps track of three things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the &lt;strong&gt;&lt;em&gt;repetition count&lt;/em&gt;&lt;/strong&gt;, which is simply the number of successful reviews the card has had.&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;&lt;em&gt;interval&lt;/em&gt;&lt;/strong&gt; which represents how many days should pass before the card appears again.&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;&lt;em&gt;easiness factor&lt;/em&gt;&lt;/strong&gt; which represents how difficult the card is for you personally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;when a card is first introduced, its easiness factor usually starts at &lt;strong&gt;2.5&lt;/strong&gt;. this value will change over time depending on how well you recall the card.&lt;/p&gt;

&lt;p&gt;whenever you review a card, you grade your recall using a score from &lt;strong&gt;0 to 5&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a score of 5 means the answer was recalled perfectly.
&lt;/li&gt;
&lt;li&gt;a score of 4 means correct but with hesitation.
&lt;/li&gt;
&lt;li&gt;a score of 3 means correct but difficult to recall.
&lt;/li&gt;
&lt;li&gt;a score of 2 or lower indicates failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if the score is below 3, the algorithm assumes the card has effectively been forgotten. the repetition counter resets and the interval goes back to one day so that the card can be relearned quickly.&lt;/p&gt;

&lt;p&gt;if the recall was successful, the interval grows.&lt;/p&gt;

&lt;p&gt;the first successful review schedules the next review for &lt;strong&gt;1 day later&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
the second successful review schedules it for &lt;strong&gt;6 days later&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;after that, the interval is calculated using a simple rule:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i(n) = i(n−1) × ef&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;in other words, the next interval is the previous interval multiplied by the card's easiness factor.&lt;/p&gt;

&lt;p&gt;if the ef is 2.5, the intervals might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;1 day → 6 days → 15 days → 37 days → 92 days&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;the intervals expand rapidly, reflecting the fact that well-learned information can be retained for much longer periods.&lt;/p&gt;

&lt;p&gt;but the real cleverness of sm-2 lies in how the easiness factor itself is updated.&lt;/p&gt;

&lt;p&gt;after each review, the ef is adjusted based on the recall quality using this formula:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ef = ef + (0.1 - (5 - q) × (0.08 + (5 - q) × 0.02))&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;here, &lt;code&gt;q&lt;/code&gt; represents the quality score given during the review.&lt;/p&gt;

&lt;p&gt;this formula has an intuitive effect. if a card is consistently recalled easily, the easiness factor increases slightly, causing intervals to grow faster. &lt;br&gt;
if a card is difficult to recall, the ef decreases, making the algorithm schedule reviews more frequently.&lt;/p&gt;

&lt;p&gt;to prevent intervals from collapsing completely, the algorithm enforces a minimum value:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ef ≥ 1.3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;with just these rules, the algorithm adapts the schedule for every card individually.&lt;/p&gt;

&lt;p&gt;easy cards gradually disappear into long review intervals. difficult cards keep resurfacing until they stabilize.&lt;/p&gt;

&lt;p&gt;the elegance of sm-2 is that it achieves this adaptive behavior with almost no complexity. the entire system requires only a few variables and a small update formula. there is no machine learning, no probabilistic modeling, and no large datasets involved.&lt;/p&gt;

&lt;p&gt;despite that simplicity, it works remarkably well.&lt;/p&gt;

&lt;p&gt;even decades after its creation, many spaced-repetition systems still rely on sm-2 or slight variations of it. newer algorithms attempt to model memory more precisely using statistical techniques, but sm-2 remains popular because it strikes a great balance between effectiveness and simplicity.&lt;/p&gt;

&lt;p&gt;in fact, you can implement the entire scheduling logic in just a few lines of code. here is what that looks like in go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;Repetition&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;  
    &lt;span class="n"&gt;Interval&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;  
    &lt;span class="n"&gt;EF&lt;/span&gt;         &lt;span class="kt"&gt;float64&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="kt"&gt;int&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="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repetition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  
        &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repetition&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&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="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repetition&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6&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="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repetition&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;  

    &lt;span class="n"&gt;ef&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EF&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;quality&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="m"&gt;0.08&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ef&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1.3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;ef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1.3&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ef&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this small piece of logic, you can build the core of a spaced-repetition scheduler.&lt;/p&gt;

&lt;p&gt;sm-2 is a nice reminder that powerful systems don’t always require complicated algorithms. sometimes a simple model, applied consistently, is enough to produce surprisingly effective behavior.&lt;br&gt;
in this case, a few lines of math ended up shaping how millions of people learn.&lt;/p&gt;




&lt;p&gt;originally published at: &lt;a href="https://www.umangsinha.in/blog/the-sm2-algorithm" rel="noopener noreferrer"&gt;https://www.umangsinha.in/blog/the-sm2-algorithm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>🚀 Released bitbloom v1.0.0: a high-performance Bloom filter library in Go. In this post, I cover what Bloom filters are, how I built one from scratch, and benchmarks showing 2M+ ops/sec. Space-efficient, blazing-fast. 🔗 Full breakdown inside:</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Fri, 13 Jun 2025 13:53:49 +0000</pubDate>
      <link>https://dev.to/umangsinha12/released-bitbloom-v100-a-high-performance-bloom-filter-library-in-go-in-this-post-i-cover-3ppa</link>
      <guid>https://dev.to/umangsinha12/released-bitbloom-v100-a-high-performance-bloom-filter-library-in-go-in-this-post-i-cover-3ppa</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k" class="crayons-story__hidden-navigation-link"&gt;Probabilistic Data Structures in Go: Building and Benchmarking a Bloom Filter&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/umangsinha12" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F468034%2F5be9fb90-2148-4b01-87b0-c369cad1979d.png" alt="umangsinha12 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/umangsinha12" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Umang Sinha
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Umang Sinha
                
              
              &lt;div id="story-author-preview-content-2587776" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/umangsinha12" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F468034%2F5be9fb90-2148-4b01-87b0-c369cad1979d.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Umang Sinha&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 13 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k" id="article-link-2587776"&gt;
          Probabilistic Data Structures in Go: Building and Benchmarking a Bloom Filter
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/datastructures"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;datastructures&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            14 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>go</category>
      <category>datastructures</category>
      <category>backend</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Probabilistic Data Structures in Go: Building and Benchmarking a Bloom Filter</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Fri, 13 Jun 2025 13:46:34 +0000</pubDate>
      <link>https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k</link>
      <guid>https://dev.to/umangsinha12/probabilistic-data-structures-in-go-building-and-benchmarking-a-bloom-filter-5b4k</guid>
      <description>&lt;p&gt;Imagine you're building a high-traffic web service that relies heavily on a distributed cache like Redis. For every incoming request, your service first checks the cache to avoid expensive database lookups. Sounds efficient, right?&lt;/p&gt;

&lt;p&gt;But here’s a subtle problem at scale: &lt;strong&gt;Many of the keys you look up may not be in the cache!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each of these negative lookups still involves a network round-trip to Redis, which, while fast, adds up when you're processing hundreds of thousands of misses per second.&lt;/p&gt;

&lt;p&gt;Wouldn’t it be great if you could avoid those pointless lookups altogether?&lt;/p&gt;

&lt;p&gt;This is where a &lt;strong&gt;Bloom filter&lt;/strong&gt; comes in - a clever probabilistic data structure that lives in your application’s memory. It helps you quickly answer the following question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Is this key definitely not in the cache?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the Bloom filter says “no” → skip the Redis call entirely.&lt;/p&gt;

&lt;p&gt;If it says “maybe” → go ahead and check Redis as usual. &lt;em&gt;(It's important to understand that the 'maybe' case can sometimes lead to a false positive, but we'll delve into that later.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s fast, tiny, and wildly effective at reducing unnecessary lookups in high-throughput systems. In fact, services at Google, Facebook, LinkedIn, and CDNs like Cloudflare all use Bloom filters to optimize performance at scale.&lt;/p&gt;

&lt;p&gt;In this article, we’ll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Break down how Bloom filters work (and the math behind them)&lt;/li&gt;
&lt;li&gt;Walk through a full implementation in Go&lt;/li&gt;
&lt;li&gt;Explore practical use cases, tuning strategies, and limitations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're optimizing caching logic or reducing database hits - Bloom filters are a tool worth knowing.&lt;/p&gt;




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

&lt;p&gt;A Bloom filter is a probabilistic data structure used to test whether an element is a member of a set. It can return two possible answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Definitely not present”&lt;/li&gt;
&lt;li&gt;“Possibly present”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s fast, memory-efficient, and beautifully suited for large-scale systems where precision can be traded for performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why “Probabilistic”?
&lt;/h3&gt;

&lt;p&gt;Bloom filters are “probabilistic” because they allow false positives. That is, they might say an element is present even when it’s not. But the beauty is, they never give false negatives. If the filter says something isn't there, you can trust it.&lt;/p&gt;

&lt;p&gt;This simple guarantee unlocks powerful optimizations in real-world systems.&lt;/p&gt;

&lt;p&gt;A Bloom filter starts with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bit array of size m, all initialized to 0&lt;/li&gt;
&lt;li&gt;k different hash functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add an element:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash the element k times&lt;/li&gt;
&lt;li&gt;Each hash gives you an index from the bit array&lt;/li&gt;
&lt;li&gt;Set each of those k bits to 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To check for an element:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash it the same way&lt;/li&gt;
&lt;li&gt;If any of those k bits are 0, the element is definitely not in the set&lt;/li&gt;
&lt;li&gt;If all are 1, then it might be present&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The more elements you add, the more bits get set, which increases the chances of collisions and hence, false positives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Analogy:
&lt;/h3&gt;

&lt;p&gt;Imagine you're trying to create a new Gmail account.&lt;/p&gt;

&lt;p&gt;As soon as you enter your desired email address, Google instantly tells you whether it’s available or already taken. That check feels instantaneous, but behind the scenes, it needs to search through hundreds of millions of existing email addresses.&lt;/p&gt;

&lt;p&gt;Now, Google could query a database directly every time someone enters an email, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That would be costly at scale&lt;/li&gt;
&lt;li&gt;It would expose the system to user enumeration risks if someone tries to brute-force existing usernames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, how do you check availability quickly and securely?&lt;/p&gt;

&lt;p&gt;One smart solution is to use a bloom filter.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google can maintain a bloom filter that contains all existing Gmail usernames, either fully or partially synced across regions.&lt;/li&gt;
&lt;li&gt;When you enter a new username, the frontend or a lightweight backend service checks the bloom filter first: If the filter says “definitely not present”, the username is available. If it says “might be present”, Google can then do a deeper, more secure database lookup to confirm.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This two-tiered approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces load on internal services&lt;/li&gt;
&lt;li&gt;Speeds up user feedback&lt;/li&gt;
&lt;li&gt;Avoids leaking user data through timing differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a perfect example of how Bloom filters trade a small chance of a false positive for dramatic speed and scale benefits, especially in global systems like Google’s.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Bloom Filters Work Internally:
&lt;/h2&gt;

&lt;p&gt;Bloom filters are elegant data structures that offer a blend of mathematical simplicity, space efficiency, and speed. In this section, we’ll explore how they actually work under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to add an item to the bloom filter?
&lt;/h3&gt;

&lt;p&gt;A bloom filter starts with a fixed-size bit array of length m, with all bits initialized to 0. &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.amazonaws.com%2Fuploads%2Farticles%2Ftpuqa0arg521x6w9a7r4.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.amazonaws.com%2Fuploads%2Farticles%2Ftpuqa0arg521x6w9a7r4.png" alt="Bloom Filter initialized" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each bit in this array represents whether a certain bit has been set by an inserted item.&lt;/p&gt;

&lt;p&gt;To insert an item into a bloom filter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash the item using k different hash functions.&lt;/li&gt;
&lt;li&gt;Each hash maps the item to a position i in the bit array.&lt;/li&gt;
&lt;li&gt;Set the bits at all k positions to 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example: inserting "bloom" with k = 3 might yield indices 1, 4, and 6. After insertion, the bit array looks like this:&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.amazonaws.com%2Fuploads%2Farticles%2F5rb0yogdweaguc1gdxst.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.amazonaws.com%2Fuploads%2Farticles%2F5rb0yogdweaguc1gdxst.png" alt="Bloom Filter insertion" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check membership?
&lt;/h3&gt;

&lt;p&gt;To check if an item might exist in the bloom filter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash the item with the same k hash functions.&lt;/li&gt;
&lt;li&gt;Inspect the k positions in the bit array.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;If any of the bits is 0 → the item is definitely not in the set.&lt;/li&gt;
&lt;li&gt;If all are 1 → the item is possibly in the set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, bloom filters give you a guaranteed no or a maybe yes.&lt;/p&gt;

&lt;p&gt;If we query "bloom" again, we’ll get the same indices 1, 4, and 6. All are set to 1, so the bloom filter returns true, meaning "bloom" might be present. And in this case, it’s correct. &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.amazonaws.com%2Fuploads%2Farticles%2F3lc7g155rm26xl1stv3w.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.amazonaws.com%2Fuploads%2Farticles%2F3lc7g155rm26xl1stv3w.png" alt="Bloom filter check" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s insert another word - "filter". Suppose its hash functions return indices 4, 5, and 7. The updated bit array becomes:&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.amazonaws.com%2Fuploads%2Farticles%2Fiw352xl26rayp0mzbuh0.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.amazonaws.com%2Fuploads%2Farticles%2Fiw352xl26rayp0mzbuh0.png" alt="Bloom filter second insertion" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the collision at index 4? Both "bloom" and "filter" caused that bit to be set. There’s no way to tell whether it was set by "bloom" or "filter". This ambiguity is at the heart of how bloom filters trade precision for speed and space.&lt;/p&gt;

&lt;h3&gt;
  
  
  False positives:
&lt;/h3&gt;

&lt;p&gt;Since Bloom filters rely on shared bit positions, it’s possible for unrelated items to appear present just by chance.&lt;/p&gt;

&lt;p&gt;Imagine querying a word, "maybe" whose hash functions return the indices as 1, 4 and 7.&lt;/p&gt;

&lt;p&gt;In our case, "maybe" would pass the membership check because the bits it checks were already set by other words, even though we never inserted "maybe".&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.amazonaws.com%2Fuploads%2Farticles%2Fz5ppkrj81geawvv1w0hp.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.amazonaws.com%2Fuploads%2Farticles%2Fz5ppkrj81geawvv1w0hp.png" alt="Bloom filter collision" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is called a false positive - the filter falsely claims the item might exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bloom filters never produce false negatives, but they can produce false positives.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This tradeoff is exactly what makes Bloom filters powerful: they allow blazing-fast lookups and save memory, in exchange for a small, tunable error rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The math behind it:
&lt;/h3&gt;

&lt;p&gt;To understand the false positive rate of a Bloom filter, we need to answer one question: What is the probability that all k hash bits for a queried item are set to 1, even though the item was never inserted?&lt;/p&gt;

&lt;p&gt;Let’s break it down step by step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Probability a bit is still 0 after n insertions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each insertion sets k bits in the array. So, after inserting n items, we’ve made kn bit assignments (some of which might overlap).&lt;/p&gt;

&lt;p&gt;Each bit has this probability of remaining unset after one random write: &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.amazonaws.com%2Fuploads%2Farticles%2F7kptav9dh3bldo2i62yr.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.amazonaws.com%2Fuploads%2Farticles%2F7kptav9dh3bldo2i62yr.png" alt="1-1/m" width="800" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After kn independent hashings, the probability that a specific bit is still 0 becomes: &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.amazonaws.com%2Fuploads%2Farticles%2Fi8lal9b5ms7my58gpe8w.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.amazonaws.com%2Fuploads%2Farticles%2Fi8lal9b5ms7my58gpe8w.png" alt="(1-1/m)^{kn}" width="800" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the identity: &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.amazonaws.com%2Fuploads%2Farticles%2Fvdrl8dygwynn9dcjmty1.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.amazonaws.com%2Fuploads%2Farticles%2Fvdrl8dygwynn9dcjmty1.png" alt="(1-1/m)^{kn} = e^{-kn/m}" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;for large m, we can approximate:&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.amazonaws.com%2Fuploads%2Farticles%2Fnygxehatbq47rn4cwjjm.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.amazonaws.com%2Fuploads%2Farticles%2Fnygxehatbq47rn4cwjjm.png" alt="P(bit~is~0) = e^{-kn/m}" width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Probability a bit is 1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, the probability that a bit is set to 1 is: &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.amazonaws.com%2Fuploads%2Farticles%2Fevrcbmt3ull30nl93hmg.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.amazonaws.com%2Fuploads%2Farticles%2Fevrcbmt3ull30nl93hmg.png" alt="P(bit~is~1) = 1 - e^{-kn/m}" width="800" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: False Positive Probability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, for a false positive to occur, all k bits checked during a query must be 1 (even though the item was never inserted). Assuming the hash functions are independent: &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.amazonaws.com%2Fuploads%2Farticles%2F5kro2x34j3yfba5spd7s.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.amazonaws.com%2Fuploads%2Farticles%2F5kro2x34j3yfba5spd7s.png" alt="P(false~positive) = (1-e^{-kn/m})^k" width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the false positive rate - the core metric in bloom filter design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Affects the False Positive Rate?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;n → number of items inserted&lt;/li&gt;
&lt;li&gt;m → size of the bit array&lt;/li&gt;
&lt;li&gt;k → number of hash functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to choose m and k wisely for a given n so that the false positive rate stays acceptably low.&lt;/p&gt;

&lt;p&gt;Optimal bit array size m for target false positive rate p:&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.amazonaws.com%2Fuploads%2Farticles%2F3noblfl3r2e36tndx7pb.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.amazonaws.com%2Fuploads%2Farticles%2F3noblfl3r2e36tndx7pb.png" alt="m = (-n * ln(p))/(ln2)^2" width="800" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s even an optimal value of k for given m and n:&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.amazonaws.com%2Fuploads%2Farticles%2Fvyl0yyo4bhpoi9lsboor.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.amazonaws.com%2Fuploads%2Farticles%2Fvyl0yyo4bhpoi9lsboor.png" alt="k = (m/n)ln2" width="800" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can precompute m and k when initializing your Bloom filter if you know n and your acceptable p. This minimizes the false positive rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asymptotic Complexity of bloom filters:
&lt;/h3&gt;

&lt;p&gt;Despite their probabilistic nature, bloom filters offer impressive performance guarantees. Let’s break them down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time complexity:&lt;/strong&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fg38frtk64i961puvwaqs.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.amazonaws.com%2Fuploads%2Farticles%2Fg38frtk64i961puvwaqs.png" alt="time complexity" width="439" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both operations involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running k hash functions&lt;/li&gt;
&lt;li&gt;Accessing k bits in the bit array&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since k is typically a small constant (e.g., 7-10), both insert and lookup are effectively O(1) in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Space complexity:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Bloom filter with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;m bits of storage&lt;/li&gt;
&lt;li&gt;n inserted elements&lt;/li&gt;
&lt;li&gt;k hash functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uses a total of &lt;strong&gt;O(m)&lt;/strong&gt; bits of memory.&lt;/p&gt;

&lt;p&gt;In summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A larger bit array (m) reduces the false positive rate but increases memory usage.&lt;/li&gt;
&lt;li&gt;More hash functions (k) improve precision up to a point, but after that, they degrade performance and increase collisions.&lt;/li&gt;
&lt;li&gt;The number of inserted elements (n) directly affects accuracy - a bloom filter degrades as it gets “full”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bloom filters strike a beautiful balance: tiny space, lightning-fast access, and tunable accuracy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementing a Bloom Filter in Go
&lt;/h2&gt;

&lt;p&gt;There’s no better way to understand Bloom filters than to implement one. Instead of building one from scratch here, we’ll walk through the internals of &lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom&lt;/em&gt;&lt;/a&gt; - a fast, thread-safe Bloom filter package I wrote in Go with performance and simplicity in mind.&lt;/p&gt;

&lt;p&gt;We’ll dissect the core functionality: initialization, hashing, insertion, lookups, and more, covering not just what the code does, but why it's designed that way. The goal is to showcase how a clean implementation can scale while remaining easy to reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Bloom Filter: bitbloom.New()
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Ff1zwgdbcfo2xoiey2rah.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.amazonaws.com%2Fuploads%2Farticles%2Ff1zwgdbcfo2xoiey2rah.png" alt="creating a bloom filter" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This initializes the filter for 10,000 expected elements and a 1% false positive rate. The library handles all the math for you.&lt;/p&gt;

&lt;p&gt;Internally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bitset size m and number of hash functions k are computed based on standard formulas.&lt;/li&gt;
&lt;li&gt;Values are clamped to safe ranges.&lt;/li&gt;
&lt;li&gt;The hash functions are seeded deterministically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures accuracy without wasting memory or CPU. &lt;/p&gt;

&lt;h3&gt;
  
  
  Optimal Parameter Calculations:
&lt;/h3&gt;

&lt;p&gt;Bloom filters rely on mathematical precision for tuning space and accuracy:&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.amazonaws.com%2Fuploads%2Farticles%2Fj5ej2yqzc56qeiej2ly1.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.amazonaws.com%2Fuploads%2Farticles%2Fj5ej2yqzc56qeiej2ly1.png" alt="optimal params calculation" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom&lt;/em&gt;&lt;/a&gt;, these are implemented as: &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.amazonaws.com%2Fuploads%2Farticles%2Frcfcmuopa9cs8v5qfhxv.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.amazonaws.com%2Fuploads%2Farticles%2Frcfcmuopa9cs8v5qfhxv.png" alt="optimal params functions" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These help you reason about the internal capacity of your filter and are exported so users can use them manually if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Items: Add(data []byte)
&lt;/h3&gt;

&lt;p&gt;Adding an item to the filter involves hashing the data k times and setting the resulting k positions in the bitset to 1. Here’s the simplified flow:&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.amazonaws.com%2Fuploads%2Farticles%2Fmaow54cwq8jtgy8yzkdm.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.amazonaws.com%2Fuploads%2Farticles%2Fmaow54cwq8jtgy8yzkdm.png" alt="add items" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We lock with a write mutex since the bitset will be mutated.&lt;/li&gt;
&lt;li&gt;The bf.hasher.Hashes() function returns k positions using a MurmurHash-based mechanism.&lt;/li&gt;
&lt;li&gt;We set those bit positions.&lt;/li&gt;
&lt;li&gt;The item count is incremented for optional introspection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing Items for membership: Test(data []byte)
&lt;/h3&gt;

&lt;p&gt;Checking if an item might be in the filter is similar - we compute the same k hash positions and verify that all are set:&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.amazonaws.com%2Fuploads%2Farticles%2Fdr4rov4plpfe3rd0rm2h.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.amazonaws.com%2Fuploads%2Farticles%2Fdr4rov4plpfe3rd0rm2h.png" alt="test for membership" width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use a read lock for high concurrency.&lt;/li&gt;
&lt;li&gt;If any of the positions are unset, we’re certain the item wasn’t added.&lt;/li&gt;
&lt;li&gt;If all positions are set, we might have seen the item before - a false positive is possible but bounded by p.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Thread Safety and Internals:
&lt;/h3&gt;

&lt;p&gt;The filter is guarded by a sync.RWMutex, allowing multiple reads but exclusive writes - enabling safe concurrent usage even under heavy load. Here's what the struct looks like: &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.amazonaws.com%2Fuploads%2Farticles%2Fd5nxdh5i00kvvy3428b6.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.amazonaws.com%2Fuploads%2Farticles%2Fd5nxdh5i00kvvy3428b6.png" alt="main bloomfilter struct" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hasher interface provides flexibility. The default implementation uses MurmurHash3 for speed and uniformity. &lt;/p&gt;

&lt;h3&gt;
  
  
  MurmurHash-Based Hashing:
&lt;/h3&gt;

&lt;p&gt;Instead of relying on Go’s built-in hashes (which are not portable or deterministic), &lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom&lt;/em&gt;&lt;/a&gt; implements a MurmurHash3-based custom hasher built using the excellent murmur library by Sébastien Paolacci (&lt;a href="https://github.com/spaolacci/murmur3" rel="noopener noreferrer"&gt;https://github.com/spaolacci/murmur3&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The interface:&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.amazonaws.com%2Fuploads%2Farticles%2Fkdev1g5icl3j8eisb2i3.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.amazonaws.com%2Fuploads%2Farticles%2Fkdev1g5icl3j8eisb2i3.png" alt="hasher interface" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast: MurmurHash is extremely performant and produces well-distributed hashes.&lt;/li&gt;
&lt;li&gt;Deterministic: Same input → same positions → consistent behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hash values are derived in a way similar to double hashing, which reduces the cost of generating k distinct hashes from just 2 base hashes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency Considerations:
&lt;/h3&gt;

&lt;p&gt;Go encourages writing concurrent programs and &lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom&lt;/em&gt;&lt;/a&gt;  is designed to embrace that.&lt;/p&gt;

&lt;p&gt;Key design points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sync.RWMutex for safe parallel reads.&lt;/li&gt;
&lt;li&gt;Internal locking means you don’t need to wrap access in your own sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows users to run millions of goroutines hitting the filter simultaneously with minimal contention, as demonstrated in the stress test section later. &lt;/p&gt;

&lt;h3&gt;
  
  
  Install &amp;amp; Use:
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Ftemy5y4lnqffihxb5idj.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.amazonaws.com%2Fuploads%2Farticles%2Ftemy5y4lnqffihxb5idj.png" alt="go get github.com/umang-sinha/bitbloom" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next section, we’ll push this filter to the limits with a real-world stress test, hitting millions of ops per second across thousands of goroutines.&lt;/p&gt;




&lt;h3&gt;
  
  
  Performance &amp;amp; Accuracy Benchmarking:
&lt;/h3&gt;

&lt;p&gt;A Bloom filter is a probabilistic data structure, but it shouldn't be probabilistically slow.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom&lt;/em&gt;&lt;/a&gt; with performance and concurrency in mind. But how well does it hold up under serious load?&lt;/p&gt;

&lt;p&gt;Let’s benchmark the library across three fronts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Raw throughput (ops/sec under concurrency)&lt;/li&gt;
&lt;li&gt;False positive accuracy (does it match the theoretical bound?)&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Stress Testing: Ops per Second&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wrote a Go program that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spawns 1,000+ goroutines&lt;/li&gt;
&lt;li&gt;Each performs thousands of Add() and Test() ops&lt;/li&gt;
&lt;li&gt;Shares a single Bloom filter instance&lt;/li&gt;
&lt;li&gt;Times the execution and prints throughput&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F6ojphtpv2ps09o8cvtji.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.amazonaws.com%2Fuploads%2Farticles%2F6ojphtpv2ps09o8cvtji.png" alt="main function benchmark" width="800" height="1259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example outputs on an 8-core machine: &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.amazonaws.com%2Fuploads%2Farticles%2Fl6n1n6tel9diub45hh4d.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.amazonaws.com%2Fuploads%2Farticles%2Fl6n1n6tel9diub45hh4d.png" alt="bloom filter benchmark 1" width="800" height="155"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fsotbgmhfi5m1wsfqa559.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.amazonaws.com%2Fuploads%2Farticles%2Fsotbgmhfi5m1wsfqa559.png" alt="bloom filter benchmark 2" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Bloom filter consistently performs in the 2.2M–2.6M ops/sec range under realistic conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy Test: False Positive Rate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I inserted 1,000,000 unique items, and then tested 1,000,000 unseen keys to measure the false positive rate:&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.amazonaws.com%2Fuploads%2Farticles%2Fq9oancjbg6b2fc43uqa8.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.amazonaws.com%2Fuploads%2Farticles%2Fq9oancjbg6b2fc43uqa8.png" alt="false positive rate code" width="800" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.amazonaws.com%2Fuploads%2Farticles%2Fxq8oj7nwkd0wyxe75vnz.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.amazonaws.com%2Fuploads%2Farticles%2Fxq8oj7nwkd0wyxe75vnz.png" alt="false positive rate result" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expected result: Around ~1% (since we set p = 0.01)&lt;/p&gt;

&lt;p&gt;Observed result: Matches closely across runs &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmarking memory usage:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran some benchmarks using runtime.ReadMemStats to compare memory usage just after initialization and after a million insertions.&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.amazonaws.com%2Fuploads%2Farticles%2Fsgw5p3ku0miel2ieh2l4.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.amazonaws.com%2Fuploads%2Farticles%2Fsgw5p3ku0miel2ieh2l4.png" alt="memory usage benchmark code" width="800" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output:&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.amazonaws.com%2Fuploads%2Farticles%2F7udlnyeq21s5jwg236g0.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.amazonaws.com%2Fuploads%2Farticles%2F7udlnyeq21s5jwg236g0.png" alt="memory usage benchmark result" width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much heap is allocated (Alloc)&lt;/li&gt;
&lt;li&gt;How much total memory has been used (TotalAlloc)&lt;/li&gt;
&lt;li&gt;How much has been reserved by Go (Sys)&lt;/li&gt;
&lt;li&gt;How many GC cycles ran&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The actual in-use memory (Alloc) stays extremely low (~2.2 MB), which makes sense since:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Bloom filter’s memory usage depends only on m (size of bitset) and not on number of insertions.&lt;/li&gt;
&lt;li&gt;I am using a compact bitset (e.g., m bits ≈ ~1–2 million bits → ~0.25 MB).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;100MB TotalAlloc is from temporary allocations during:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hashing operations&lt;/li&gt;
&lt;li&gt;Slice copying&lt;/li&gt;
&lt;li&gt;Other per-insertion allocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go’s GC is working well - 38 GCs for 1M insertions is not excessive, and memory use is staying bounded. &lt;/p&gt;




&lt;h3&gt;
  
  
  Real-World Applications:
&lt;/h3&gt;

&lt;p&gt;Bloom filters aren’t just a theoretical curiosity - they’re used extensively in high-performance systems to reduce latency, avoid unnecessary computation, and optimize memory usage. Here are some real-world use cases where Bloom filters shine:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Databases and Storage Engines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many databases use Bloom filters to minimize costly disk lookups. For example, Apache HBase employs Bloom filters at the block level to quickly determine whether a row or column might exist in a file, avoiding unnecessary reads from disk. Similarly, Cassandra and LevelDB rely on Bloom filters to reduce the number of SSTables accessed during queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Caching Systems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bloom filters are often used as a read-through cache guard to prevent cache penetration. Suppose a backend cache (like Redis or Memcached) receives frequent requests for keys that don’t exist. A Bloom filter can sit in front and quickly filter out non-existent keys, reducing load on both the cache and the database behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Content Delivery Networks (CDNs)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CDNs use Bloom filters at the edge to keep track of recently served content or known malicious URLs. For instance, Google Chrome's Safe Browsing API uses Bloom filters to store hashes of unsafe URLs, allowing browsers to quickly check for potential threats without frequent server queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Security and Malware Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Security systems use Bloom filters to maintain large sets of known bad IPs, malware signatures, or suspicious domains. These can be checked quickly before performing expensive full-pattern matches or fetching threat intelligence data from external services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Distributed Systems and Network Protocols&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In distributed architectures, Bloom filters are used to reduce network chatter. A node might send a Bloom filter of its data set to another node so that the receiver can quickly determine which items it’s missing, without transferring full lists. Systems like Apache Hadoop use this pattern to reduce data shuffling during MapReduce jobs. &lt;/p&gt;




&lt;h3&gt;
  
  
  Limitations and Trade-offs
&lt;/h3&gt;

&lt;p&gt;While Bloom filters are powerful and widely used, they’re not a one-size-fits-all solution. Like any engineering tool, they come with inherent trade-offs that must be carefully considered depending on your use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. No Native Support for Deletion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard Bloom filters do not support deletions. Once an item is inserted, its presence cannot be definitively removed without risking the integrity of other items' presence bits. This limitation can be problematic in scenarios where your data set is dynamic and items frequently expire or get removed.&lt;/p&gt;

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

&lt;p&gt;A common solution is to use a Counting Bloom Filter, which replaces the underlying bit array with an array of counters. Each insertion increments the counters for the k hash positions, and each deletion decrements them. This adds memory overhead but enables safe removals at the cost of increased complexity and potential counter overflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. False Positives Are Inevitable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bloom filters never produce false negatives, but false positives are always a possibility. That means you might occasionally believe an item exists when it does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choosing the right false positive rate (p):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is critical and highly application-dependent. A lower false positive rate means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More memory consumption&lt;/li&gt;
&lt;li&gt;More hash functions (slower operations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conversely, a higher false positive rate reduces memory usage but increases the chance of incorrect "exists" results. Tuning p involves understanding the cost of false positives in your system. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In a CDN cache, a false positive might mean serving stale content - probably tolerable.&lt;/li&gt;
&lt;li&gt;In a security filter, a false positive might block a legitimate user - not acceptable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Choosing n in a Dynamic World&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bloom filters require you to specify the expected number of insertions (n) up front. Underestimating n leads to higher false positive rates; overestimating wastes memory. This is challenging for dynamic workloads or systems where data volume changes over time.&lt;/p&gt;

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

&lt;p&gt;You can use a scalable Bloom filter, which grows as needed by chaining multiple filters with increasing capacity and decreasing false positive targets. However, this adds design complexity.&lt;/p&gt;

&lt;p&gt;Bloom filters offer an impressive blend of speed and space efficiency, but they are not magic. A well-engineered system must balance their strengths with their limitations, especially in high-availability or high-accuracy environments. &lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion:
&lt;/h3&gt;

&lt;p&gt;Bloom filters are a powerful tool in an engineer’s toolkit - compact, fast, and surprisingly versatile. From preventing unnecessary database hits to guarding high-latency operations, they offer an elegant solution where approximate answers are good enough.&lt;/p&gt;

&lt;p&gt;In this article, we explored the theory behind Bloom filters, walked through their internals, and benchmarked their performance across speed, memory, and accuracy. Along the way, we also walked through a fully concurrent Bloom filter in Go.&lt;/p&gt;

&lt;p&gt;If you're looking to integrate a production-ready Bloom filter into your Go projects, take a look at my library, &lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom&lt;/em&gt;&lt;/a&gt; - the library we walked through earlier. It’s fast, reliable, and designed with performance in mind.&lt;/p&gt;

&lt;p&gt;Probabilistic data structures may not give you perfect answers, but in the right contexts, they’ll give you the right answers, fast.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources and further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;&lt;em&gt;bitbloom on GitHub&lt;/em&gt;&lt;/a&gt; - The Go library I built and benchmarked in this article. A production-ready Bloom filter implementation with clean API design, fast operations, and concurrency safety (&lt;a href="https://pkg.go.dev/github.com/umang-sinha/bitbloom" rel="noopener noreferrer"&gt;https://pkg.go.dev/github.com/umang-sinha/bitbloom&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Network Applications of Bloom Filters: A Survey - Broder and Mitzenmacher (&lt;a href="https://www.eecs.harvard.edu/%7Emichaelm/postscripts/im2005b.pdf" rel="noopener noreferrer"&gt;https://www.eecs.harvard.edu/~michaelm/postscripts/im2005b.pdf&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The Veracious Counting Bloom Filter - Brindha Palanisamy and Senthilkumar Athappan (&lt;a href="https://www.iajit.org/portal/PDF/vol.%2014,%20no%206/9285.pdf" rel="noopener noreferrer"&gt;https://www.iajit.org/portal/PDF/vol.%2014,%20no%206/9285.pdf&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>datastructures</category>
      <category>backend</category>
      <category>opensource</category>
    </item>
    <item>
      <title>PostgreSQL UUID Performance: Benchmarking Random (v4) and Time-based (v7) UUIDs</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Fri, 23 May 2025 03:23:16 +0000</pubDate>
      <link>https://dev.to/umangsinha12/postgresql-uuid-performance-benchmarking-random-v4-and-time-based-v7-uuids-n9b</link>
      <guid>https://dev.to/umangsinha12/postgresql-uuid-performance-benchmarking-random-v4-and-time-based-v7-uuids-n9b</guid>
      <description>&lt;p&gt;Universally Unique Identifiers (UUIDs) are 128-bit values designed to ensure uniqueness across systems, without requiring any central coordination. For UUIDv4, a sample of 3.26×10¹⁶ values has a 99.99% chance of containing no duplicates, thanks to its 122 bits of randomness [&lt;a href="https://medium.com/data-science/are-uuids-really-unique-57eb80fc2a87" rel="noopener noreferrer"&gt;source&lt;/a&gt;]. This makes them ideal for use as primary keys in a database, particularly in distributed systems.&lt;/p&gt;

&lt;p&gt;One of the most widely used UUID formats is &lt;strong&gt;UUIDv4&lt;/strong&gt;, which relies entirely on random number generation. Because they don’t encode any order or time information, UUIDv4s are inherently non-sequential.&lt;/p&gt;

&lt;p&gt;This randomness makes them excellent for ensuring uniqueness across nodes, but it also leads to poor index locality in databases like PostgreSQL, especially when used as primary keys. Each insert happens in a random location in the B-tree, which causes frequent page splits and bloated indexes over time.&lt;/p&gt;

&lt;p&gt;To address this, the IETF proposed &lt;strong&gt;UUIDv7&lt;/strong&gt;, a time-based format that embeds a millisecond-resolution Unix timestamp in the high-order bits.&lt;/p&gt;

&lt;p&gt;This results in UUIDs that retain uniqueness while also being roughly &lt;strong&gt;monotonically increasing&lt;/strong&gt;, making them far more index-friendly. UUIDv7 retains global uniqueness while offering better performance characteristics for time-ordered inserts and queries in databases like PostgreSQL.&lt;/p&gt;

&lt;p&gt;But does UUIDv7 actually perform better in practice, particularly in PostgreSQL?&lt;/p&gt;

&lt;p&gt;In this article, we'll benchmark UUIDv4 and UUIDv7 in PostgreSQL by comparing their insert speeds, index sizes, and query performance. We'll dig into how the structure of UUIDs impacts B-tree behavior, and whether switching to UUIDv7 is worth it for modern applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  UUID Versions Explained:
&lt;/h2&gt;

&lt;p&gt;UUIDs are typically represented as 36-character hexadecimal strings with hyphens. Despite their compact string appearance, they carry structured meaning depending on the version.&lt;/p&gt;

&lt;p&gt;A UUID is split into five parts: &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.amazonaws.com%2Fuploads%2Farticles%2F1zzzv4l2xyavsp8u2ewj.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.amazonaws.com%2Fuploads%2Farticles%2F1zzzv4l2xyavsp8u2ewj.png" alt="UUID Structure" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;M is the version (e.g., 4 for UUIDv4, 7 for UUIDv7).&lt;/li&gt;
&lt;li&gt;N encodes the variant (usually 10xx for RFC 4122 compliant UUIDs).&lt;/li&gt;
&lt;li&gt;The rest is either random or encodes time/data, depending on the version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  UUIDv4: Random
&lt;/h3&gt;

&lt;p&gt;UUIDv4 is the most commonly used version. It sets only two fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version = 4 (in the 13th hex digit).&lt;/li&gt;
&lt;li&gt;Variant = 10xx (in the 17th hex digit).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else is pure randomness. This ensures high entropy but results in non-sequential values.&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.amazonaws.com%2Fuploads%2Farticles%2Fmj3xmy9ge19ocis99in5.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.amazonaws.com%2Fuploads%2Farticles%2Fmj3xmy9ge19ocis99in5.png" alt="Bit-level structure of UUIDv4. Only the version and variant bits are fixed; the rest is pure randomness. " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downside&lt;/strong&gt;: Poor locality in B-tree indexes due to randomness.&lt;/p&gt;

&lt;h3&gt;
  
  
  UUIDv7: Time-based
&lt;/h3&gt;

&lt;p&gt;UUIDv7 was introduced to improve temporal ordering and index performance. It uses the high bits to encode a Unix timestamp in milliseconds, while the remaining bits are random to preserve uniqueness.&lt;/p&gt;

&lt;p&gt;Bit layout of UUIDv7:&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.amazonaws.com%2Fuploads%2Farticles%2Ffg7rzteq34oypn9hr5wu.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.amazonaws.com%2Fuploads%2Farticles%2Ffg7rzteq34oypn9hr5wu.png" alt=" Bit layout of UUIDv7 " width="455" height="319"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fomevln1exnptztpbex1y.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.amazonaws.com%2Fuploads%2Farticles%2Fomevln1exnptztpbex1y.png" alt="Bit-level structure of UUIDv7. The high bits represent time; the rest remains random to avoid collisions." width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefit&lt;/strong&gt;: Maintains insertion order in databases, improving index locality and reducing write amplification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Key Locality Matters in PostgreSQL:
&lt;/h2&gt;

&lt;p&gt;Choosing the right primary key doesn’t just influence how your data is uniquely identified, it also has a profound impact on how efficiently that data is stored, indexed, and retrieved. One often-overlooked consideration is how your key choice affects data locality and write performance within the database engine. &lt;/p&gt;

&lt;p&gt;PostgreSQL, like many relational databases, uses B-tree indexes to organize and access primary key values. These indexes store keys in sorted order, making them highly efficient for range queries and lookups, but also sensitive to the order in which keys are inserted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How B-tree Indexes Work in PostgreSQL:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A B-tree in PostgreSQL is made up of fixed-size pages, usually 8 KB in size, that hold sorted key-value entries. When a new row is inserted into a table with a B-tree-indexed primary key, PostgreSQL traverses the tree to find the appropriate page where the new key belongs. If the target page has space, the new entry is inserted directly. But if the page is full, PostgreSQL splits it into two pages: one holding the lower half of the entries, and the other the upper half. The tree is then updated to reflect this structural change.&lt;/p&gt;

&lt;p&gt;Page splits are not just computationally expensive, but they also result in additional I/O, increased write amplification, and potential index bloat. Over time, a heavily fragmented index becomes slower to write to and less efficient to read from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Random UUIDs (v4) Hurt Performance:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUIDv4 is popular for primary keys because it provides excellent randomness and extremely low collision risk. However, this randomness comes at a cost. &lt;/p&gt;

&lt;p&gt;Because UUIDv4 values are entirely random, new entries are inserted into arbitrary positions in the B-tree. PostgreSQL cannot make any assumptions about where the next UUID will fall in the keyspace and hence every insert effectively becomes a random-access write. This behaviour leads to frequent page splits as new keys collide with existing ones across the tree. &lt;/p&gt;

&lt;p&gt;Over time, this causes the index to bloat, increases write amplification, and reduces the effectiveness of caching, since recently used index pages are unlikely to be reused soon. Additionally, queries that rely on ordered traversal, such as &lt;strong&gt;&lt;em&gt;ORDER BY id DESC&lt;/em&gt;&lt;/strong&gt; or cursor-based pagination using &lt;strong&gt;&lt;em&gt;WHERE id &amp;gt; ?&lt;/em&gt;&lt;/strong&gt; suffer from poor performance because the data is scattered non-sequentially throughout the tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why UUIDv7 Fixes This:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUIDv7 was introduced to solve this very problem. It embeds a 48-bit Unix timestamp (in milliseconds) into the most significant bits of the UUID, resulting in values that are roughly time-ordered.&lt;/p&gt;

&lt;p&gt;This means that UUIDv7 values are monotonically increasing over time, which dramatically improves index locality. As new records are inserted, their UUIDv7 keys tend to fall at the end of the B-tree. This significantly reduces the likelihood of page splits, minimizes fragmentation, and allows PostgreSQL to optimize for sequential writes.&lt;/p&gt;

&lt;p&gt;Because of this time-based structure, UUIDv7 provides behavior similar to that of auto-incrementing integers, but without sacrificing the global uniqueness and decentralization benefits that UUIDs offer. The timestamp ensures order, while the random bits in the lower portion of the UUID maintain uniqueness even across distributed systems.&lt;/p&gt;

&lt;p&gt;In practice, systems using UUIDv7 as a primary key observe lower write amplification, reduced disk I/O, and faster performance for queries that involve ordered traversal or cursor-based pagination. The B-tree remains compact and more predictable, which also improves performance under high write loads or concurrent inserts.&lt;/p&gt;

&lt;p&gt;While UUIDv4 excels in uniqueness, UUIDv7 offers a practical compromise, retaining uniqueness while gaining the efficiency of ordered inserts.&lt;/p&gt;

&lt;p&gt;In summary:&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.amazonaws.com%2Fuploads%2Farticles%2Fj4w6yrwaljdwcygv5ql2.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.amazonaws.com%2Fuploads%2Farticles%2Fj4w6yrwaljdwcygv5ql2.png" alt="UUIDv4 vs UUIDv7 comparison" width="792" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment Setup:
&lt;/h2&gt;

&lt;p&gt;To evaluate the practical impact of UUIDv4 vs UUIDv7 on PostgreSQL performance, we will run benchmarks using identical table structures, data and insertion logic. The only variable change will be the UUID version used for the primary key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a. Database Configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I will be using PostgreSQL 16 for the benchmark, hosted locally inside a docker container on a system with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU: 8 cores&lt;/li&gt;
&lt;li&gt;Memory: 16 GB RAM&lt;/li&gt;
&lt;li&gt;Disk: NVMe SSD&lt;/li&gt;
&lt;li&gt;Extensions: None required, since UUIDs will be generated client-side using Go.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will be using pgAdmin 4 to run any SQL queries against the database. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b. Table Schema:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two tables are created with the exact same structure. Only the key generation strategy differs.&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.amazonaws.com%2Fuploads%2Farticles%2Fmg0mc3x2hzjth365dubw.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.amazonaws.com%2Fuploads%2Farticles%2Fmg0mc3x2hzjth365dubw.png" alt="Create Postgres tables" width="766" height="804"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each row will have a UUID key and a small random string in the payload column to simulate realistic row sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;c. UUID Generation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To eliminate any bias in benchmarking, both UUIDv4 and UUIDv7 values will be generated using the same Go script, in memory, before the insert operation starts. This allows us to isolate and measure only the time taken by the database to perform inserts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UUIDv4: generated using &lt;a href="https://pkg.go.dev/github.com/google/uuid" rel="noopener noreferrer"&gt;github.com/google/uuid&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;UUIDv7: generated using &lt;a href="https://pkg.go.dev/github.com/samborkent/uuidv7" rel="noopener noreferrer"&gt;github.com/samborkent/uuidv7&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No bias from generation latency during insert timing&lt;/li&gt;
&lt;li&gt;Uniform client-side CPU and memory usage&lt;/li&gt;
&lt;li&gt;Identical batching and transaction logic for inserts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will pre-generate the full dataset (UUID + payload) in slices of structs, and measure only the database insertion time, excluding UUID and payload generation from the timing. The insertions will be performed using parameterized queries in batches (e.g. 10,000 rows per batch) using &lt;em&gt;database/sql&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;d. Go script responsibilities:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Go benchmark script will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate 10 million UUIDs of each type (v4 and v7)&lt;/li&gt;
&lt;li&gt;Pair each UUID with a random payload string&lt;/li&gt;
&lt;li&gt;Store the entries in memory&lt;/li&gt;
&lt;li&gt;Insert the entries into the respective tables in batches&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This setup ensures we're isolating the effect of UUID key locality on B-tree index behaviour without being skewed by unrelated overhead. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmarking Script:
&lt;/h2&gt;

&lt;p&gt;To isolate and accurately measure the impact of UUID version on insert and query performance, we will write a Go benchmarking script that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates 10 million UUIDs and payloads in memory&lt;/li&gt;
&lt;li&gt;Times only the insert phase, excluding UUID generation and payload creation time.&lt;/li&gt;
&lt;li&gt;Performs batch inserts using PostgreSQL’s &lt;strong&gt;pq&lt;/strong&gt; driver&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;a. Dependencies:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will use the following Go packages: &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.amazonaws.com%2Fuploads%2Farticles%2Fwr266y9510u1frbw82qc.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.amazonaws.com%2Fuploads%2Farticles%2Fwr266y9510u1frbw82qc.png" alt="go imports" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b. UUID and Payload Generation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before benchmarking inserts, we generate all UUIDs and payloads in memory:&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.amazonaws.com%2Fuploads%2Farticles%2Fw17fb7u6nmbht1zfelh3.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.amazonaws.com%2Fuploads%2Farticles%2Fw17fb7u6nmbht1zfelh3.png" alt="UUID and Payload Generation" width="800" height="761"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;c. Insert Logic:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use PostgreSQL's &lt;strong&gt;pq&lt;/strong&gt; driver to perform batch inserts of size 10,000 rows:&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.amazonaws.com%2Fuploads%2Farticles%2Fjqvmfzzy0coyohph35h2.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.amazonaws.com%2Fuploads%2Farticles%2Fjqvmfzzy0coyohph35h2.png" alt="Insert Logic" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;d. Execution:&lt;/strong&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fbdzzuqtmv1reckuinh2f.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.amazonaws.com%2Fuploads%2Farticles%2Fbdzzuqtmv1reckuinh2f.png" alt="execution" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Both UUIDs and payloads are generated before timing begins to ensure we are measuring only database performance&lt;/li&gt;
&lt;li&gt;Batching improves performance and mirrors how real-world services insert data at scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benchmark Execution:
&lt;/h2&gt;

&lt;p&gt;Before diving into raw performance numbers, I would love to demonstrate a key property of UUIDv7 - monotonicity.&lt;/p&gt;

&lt;p&gt;Unlike UUIDv4 (which is completely random), UUIDv7 is designed to be time-ordered, embedding the current Unix timestamp (in milliseconds) into the most significant bits of the UUID. This allows for natural sortability, better index locality, and potential performance advantages for write-heavy workloads.&lt;/p&gt;

&lt;p&gt;Here’s a set of UUIDv7 values I generated in Go, pausing for 1 millisecond between each call:&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.amazonaws.com%2Fuploads%2Farticles%2Fb8z0e3tnhfg6c541ikbl.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.amazonaws.com%2Fuploads%2Farticles%2Fb8z0e3tnhfg6c541ikbl.png" alt="go run main.go" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you observe closely, the hexadecimal digits in the second segment of each UUID (after the first hyphen) are gradually increasing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;2f2d → 2f2e → 2f30 → 2f31 → … → 2f37&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This confirms that UUIDv7 values preserve insertion order, which should result in fewer B-tree page splits in PostgreSQL and better index write locality - a hypothesis we will validate in the benchmarks below.&lt;br&gt;
Insert Performance:&lt;/p&gt;

&lt;p&gt;I inserted 10 million rows into each table using batched inserts (10,000 rows per batch), with UUIDs and payloads pre-generated in memory to ensure the measurement reflects only database insertion time. &lt;/p&gt;

&lt;h3&gt;
  
  
  Insert Performance:
&lt;/h3&gt;

&lt;p&gt;I inserted 10 million rows into each table using batched inserts (10,000 rows per batch), with UUIDs and payloads pre-generated in memory to ensure the measurement reflects only database insertion time.&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.amazonaws.com%2Fuploads%2Farticles%2F2ocuty0gp4nozobpebj8.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.amazonaws.com%2Fuploads%2Farticles%2F2ocuty0gp4nozobpebj8.png" alt="go run main.go" width="800" height="193"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fzrczi9uk02uq5244tx1q.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.amazonaws.com%2Fuploads%2Farticles%2Fzrczi9uk02uq5244tx1q.png" alt="insert performance" width="523" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;i. UUIDv7 inserts were ~34.8% faster than UUIDv4 inserts.&lt;/p&gt;

&lt;p&gt;ii. The performance gain is due to UUIDv7’s monotonic nature, which improves B-tree index locality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UUIDv4 inserts scatter randomly across the index, causing frequent page splits and higher I/O overhead.&lt;/li&gt;
&lt;li&gt;UUIDv7 inserts append in order, minimizing page splits and promoting sequential writes within index pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;iii. This performance improvement becomes more pronounced as the table grows and the B-tree index gets deeper.&lt;/p&gt;

&lt;p&gt;In a high-insert workload (like logs, events, or user activity tracking), switching from UUIDv4 to UUIDv7 can yield tangible write performance benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disk Usage:
&lt;/h3&gt;

&lt;p&gt;To assess how the UUID type affects storage footprint, I measured the total relation size (table + index) using: &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.amazonaws.com%2Fuploads%2Farticles%2F3fxont5q23nxcuabyi8d.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.amazonaws.com%2Fuploads%2Farticles%2F3fxont5q23nxcuabyi8d.png" alt="table size in postgres" width="800" height="289"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F0ngflajualaaay6oes05.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.amazonaws.com%2Fuploads%2Farticles%2F0ngflajualaaay6oes05.png" alt="uuidv4 table size" width="800" height="277"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fj2n5k7mggpqr2tfqunjz.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.amazonaws.com%2Fuploads%2Farticles%2Fj2n5k7mggpqr2tfqunjz.png" alt="uuidv7 table size" width="800" height="275"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fsaflubwuy7ejmapqo3k2.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.amazonaws.com%2Fuploads%2Farticles%2Fsaflubwuy7ejmapqo3k2.png" alt="uuidv4 vs uuidv7 table size" width="407" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;i. The UUIDv7 table uses ~175 MB less disk space than UUIDv4, despite having the same number of rows and exactly same schema.&lt;/p&gt;

&lt;p&gt;ii. This can be attributed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Index locality: UUIDv7s are monotonically increasing, leading to sequential inserts and more compact B-tree indexes.&lt;/li&gt;
&lt;li&gt;Fewer page splits and better fill factor due to reduced randomness in the index keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;iii. UUIDv4, being completely random, causes heavier index fragmentation, leading to larger storage usage.&lt;/p&gt;

&lt;p&gt;This highlights that UUIDv7 not only improves insert performance but is also more storage-efficient, especially at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Index Size:
&lt;/h3&gt;

&lt;p&gt;In addition to measuring the total disk usage, I also analyzed the disk footprint of the primary key indexes. Since both tables use a &lt;strong&gt;&lt;em&gt;UUID PRIMARY KEY&lt;/em&gt;&lt;/strong&gt;, PostgreSQL automatically creates a B-tree index on the id column.&lt;/p&gt;

&lt;p&gt;I queried the size of the index alone using the following query: &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.amazonaws.com%2Fuploads%2Farticles%2Fyn8bm35qnyfl45qt90ke.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.amazonaws.com%2Fuploads%2Farticles%2Fyn8bm35qnyfl45qt90ke.png" alt="get index size in postgres" width="800" height="517"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F8fzbf1d096eubeul5q84.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.amazonaws.com%2Fuploads%2Farticles%2F8fzbf1d096eubeul5q84.png" alt="uuidv4 vs uuidv7 index size" width="800" height="415"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Ffzapsxkwqzxtka0lzylh.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.amazonaws.com%2Fuploads%2Farticles%2Ffzapsxkwqzxtka0lzylh.png" alt="uuidv4 vs uuidv7 index size" width="619" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;i. The index built on UUIDv7 is 174 MB smaller than the one on UUIDv4.&lt;/p&gt;

&lt;p&gt;ii. This translates to a ~22% reduction in index size.&lt;/p&gt;

&lt;p&gt;iii. The difference is a direct result of UUIDv7's monotonic nature, which provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved index locality&lt;/li&gt;
&lt;li&gt;Fewer B-tree page splits&lt;/li&gt;
&lt;li&gt;Tighter physical clustering of keys&lt;/li&gt;
&lt;li&gt;Better cache utilization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smaller indexes improve read performance, particularly for range scans and point lookups.&lt;/p&gt;

&lt;p&gt;They also reduce I/O pressure, making UUIDv7 a better choice for write-heavy and read-latency-sensitive workloads at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Performance:
&lt;/h3&gt;

&lt;p&gt;I measured point lookup and range scan performance for both UUIDv4 and UUIDv7 using the following queries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Point Lookup:&lt;/strong&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F44xekmpvw42rbyokwba4.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.amazonaws.com%2Fuploads%2Farticles%2F44xekmpvw42rbyokwba4.png" alt="point lookup query" width="800" height="201"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fc6oh5hvd3ke2raq18zd0.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.amazonaws.com%2Fuploads%2Farticles%2Fc6oh5hvd3ke2raq18zd0.png" alt="point lookup uuidv4 result" width="800" height="278"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fm0uk2abryx269ta16i27.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.amazonaws.com%2Fuploads%2Farticles%2Fm0uk2abryx269ta16i27.png" alt="point lookup uuidv7 result" width="800" height="265"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fohathuep7vdlvohpipgd.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.amazonaws.com%2Fuploads%2Farticles%2Fohathuep7vdlvohpipgd.png" alt="point lookup uuidv4 vs uuidv7" width="598" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;UUIDv7 has significantly lower planning and execution times than UUIDv4.&lt;/li&gt;
&lt;li&gt;UUIDv7's monotonically increasing nature improves the index's locality, leading to faster lookups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Range scan:&lt;/strong&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fuk6sosnr2epx8yuaaxs8.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.amazonaws.com%2Fuploads%2Farticles%2Fuk6sosnr2epx8yuaaxs8.png" alt="range scan query" width="800" height="262"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fkx2r5mjmx9h6ilfx9dsm.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.amazonaws.com%2Fuploads%2Farticles%2Fkx2r5mjmx9h6ilfx9dsm.png" alt="range scan uuidv4" width="800" height="260"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F2v9228ti5yz6r0x7o5dv.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.amazonaws.com%2Fuploads%2Farticles%2F2v9228ti5yz6r0x7o5dv.png" alt="range scan uuidv7" width="800" height="288"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Faw8aptzgxwtwdynuens7.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.amazonaws.com%2Fuploads%2Farticles%2Faw8aptzgxwtwdynuens7.png" alt="range scan uuidv4 vs uuidv7" width="592" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;While UUIDv7 takes slightly more time during planning, its execution time is much faster.&lt;/li&gt;
&lt;li&gt;The sequential nature of UUIDv7 reduces index fragmentation, providing quicker access to sequential data, thus improving range scan performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;UUIDv7 outperforms UUIDv4 in both point lookups and range scans, with lower execution times, thanks to its monotonic sequence.&lt;/p&gt;

&lt;p&gt;The lower disk usage and faster query performance make UUIDv7 a more efficient choice for databases, especially when querying large datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Considerations:
&lt;/h2&gt;

&lt;p&gt;While UUIDv7 clearly demonstrates performance and storage advantages, choosing it in production should still account for a few practical factors:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros of UUIDv7:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Monotonicity = Speed: Writes are faster due to better index locality and reduced page splits.&lt;/li&gt;
&lt;li&gt;Smaller Indexes: Less disk space, better cache efficiency.&lt;/li&gt;
&lt;li&gt;Faster Range Queries: Naturally sortable and ideal for time-ordered data (e.g., logs, events, timelines).&lt;/li&gt;
&lt;li&gt;Globally Unique + Time Encoded: You get the benefits of a UUID plus implicit timestamping.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Caveats:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tooling &amp;amp; Compatibility: Some older systems, libraries, or languages may not support UUIDv7 yet.&lt;/li&gt;
&lt;li&gt;Randomness &amp;amp; Privacy: UUIDv7 includes a timestamp. If your use case demands anonymity or unpredictability, consider this a tradeoff.&lt;/li&gt;
&lt;li&gt;Availability in Libraries: While UUIDv4 is standard and widely supported, UUIDv7 still requires third-party packages in many ecosystems.
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This benchmark set out to answer a simple question: “&lt;em&gt;Is UUIDv7 actually better than UUIDv4 in PostgreSQL?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;The results speak for themselves:&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.amazonaws.com%2Fuploads%2Farticles%2Ffdvbc10a0addn2n5xamz.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.amazonaws.com%2Fuploads%2Farticles%2Ffdvbc10a0addn2n5xamz.png" alt="uuidv4 vs uuidv7 metrics" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In summary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UUIDv7 not only preserves global uniqueness but also enhances PostgreSQL performance in meaningful ways.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building systems that scale, especially write-heavy ones, it's a very strong candidate.&lt;/p&gt;

&lt;p&gt;All code used in this benchmark, including UUID generation, PostgreSQL schema, and Go benchmarking logic is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/umang-sinha/postgres-uuid-benchmark" rel="noopener noreferrer"&gt;https://github.com/umang-sinha/postgres-uuid-benchmark&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to fork, run, or modify it for your own experiments!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources and further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Why UUIDv7 is Revolutionizing Time-Ordered Identifiers - &lt;a href="https://corner.buka.sh/why-uuidv7-is-revolutionizing-time-ordered-identifiers-for-modern-systems/" rel="noopener noreferrer"&gt;https://corner.buka.sh/why-uuidv7-is-revolutionizing-time-ordered-identifiers-for-modern-systems/&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;UUIDs Are Bad for Database Index Performance - Enter UUIDv7! - &lt;a href="https://www.toomanyafterthoughts.com/uuids-are-bad-for-database-index-performance-uuid7/" rel="noopener noreferrer"&gt;https://www.toomanyafterthoughts.com/uuids-are-bad-for-database-index-performance-uuid7/&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Unexpected Downsides of UUID Keys in PostgreSQL - &lt;a href="https://www.cybertec-postgresql.com/en/unexpected-downsides-of-uuid-keys-in-postgresql/" rel="noopener noreferrer"&gt;https://www.cybertec-postgresql.com/en/unexpected-downsides-of-uuid-keys-in-postgresql/&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;How PostgreSQL Indexes Can Negatively Impact Performance - &lt;a href="https://www.percona.com/blog/postgresql-indexes-can-hurt-you-negative-effects-and-the-costs-involved/" rel="noopener noreferrer"&gt;https://www.percona.com/blog/postgresql-indexes-can-hurt-you-negative-effects-and-the-costs-involved/&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Benchmarking UUIDv4 vs UUIDv7 in PostgreSQL - &lt;a href="https://mblum.me/posts/pg-uuidv7-benchmark" rel="noopener noreferrer"&gt;https://mblum.me/posts/pg-uuidv7-benchmark&lt;/a&gt;/&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>postgres</category>
      <category>go</category>
      <category>database</category>
    </item>
    <item>
      <title>Beyond JavaScript - Why 0.1 + 0.2 doesn't equal 0.3 in programming</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Fri, 13 Sep 2024 13:44:48 +0000</pubDate>
      <link>https://dev.to/umangsinha12/beyond-javascript-why-01-02-doesnt-equal-03-in-programming-2bf3</link>
      <guid>https://dev.to/umangsinha12/beyond-javascript-why-01-02-doesnt-equal-03-in-programming-2bf3</guid>
      <description>&lt;p&gt;JavaScript is frequently ridiculed when developers first encounter this seemingly baffling result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.1 + 0.2 == 0.30000000000000004
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Memes about JavaScript's handling of numbers are widespread, often leading many to believe that this behaviour is unique to the language. &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.amazonaws.com%2Fuploads%2Farticles%2Frdugeb0xx1izvjlkpg1h.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.amazonaws.com%2Fuploads%2Farticles%2Frdugeb0xx1izvjlkpg1h.png" alt="Meme about quirky arithmetic in JS" width="640" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, this quirk isn't just limited to JavaScript. It is a consequence of how most programming languages handle floating-point arithmetic.&lt;/p&gt;

&lt;p&gt;For instance, here are code snippets from &lt;strong&gt;Java&lt;/strong&gt; and &lt;strong&gt;Go&lt;/strong&gt; that produce similar results:&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.amazonaws.com%2Fuploads%2Farticles%2Fbz2753u44c83hvhzsvic.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.amazonaws.com%2Fuploads%2Farticles%2Fbz2753u44c83hvhzsvic.png" alt="quirky arithmetic in Java" width="800" height="145"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fmvwm8dp77y8jcvo9bkzl.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.amazonaws.com%2Fuploads%2Farticles%2Fmvwm8dp77y8jcvo9bkzl.png" alt="quirky arithmetic in Go" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Computers can natively only store integers. They don't understand fractions. (How will they? The only way computers can do arithmetic is by turning some lights on or off. The light can either be on or off. It can't be "half" on!) They need some way of representing floating point numbers. Since this representation is not perfectly accurate, more often than not, 0.1 + 0.2 does not equal 0.3.&lt;/p&gt;

&lt;p&gt;All fractions whose denominators are made of prime factors of the number system's base can be cleanly expressed while any other fractions would have repeating decimals. For example, in the number system with base 10, fractions like 1/2, 1/4, 1/5, 1/10 are cleanly represented because the denominators in each case are made up of 2 or 5 - the prime factors of 10. However, fractions like 1/3, 1/6, 1/7 all have recurring decimals.&lt;/p&gt;

&lt;p&gt;Similarly, in the binary system fractions like 1/2, 1/4, 1/8 are cleanly expressed while all other fractions have recurring decimals. When you perform arithmetic on these recurring decimals, you end up with leftovers which carry over when you convert the computer's binary representation of numbers to a human readable base-10 representation. This is what leads to approximately correct results.&lt;/p&gt;

&lt;p&gt;Now that we've established that this problem is not exclusive to JavaScript, let's explore how floating-point numbers are represented and processed under the hood to understand why this behaviour occurs.&lt;/p&gt;

&lt;p&gt;In order to understand how floating point numbers are represented and processed under the hood, we would first have to understand the &lt;strong&gt;IEEE 754&lt;/strong&gt; floating point standard.  &lt;/p&gt;

&lt;p&gt;IEEE 754 standard is a widely used specification for representing and performing arithmetic on floating-point numbers in computer systems. It was created to guarantee consistency when using floating-point arithmetic on various computing platforms. Most programming languages and hardware implementations (CPUs, GPUs, etc.) adhere to this standard.&lt;/p&gt;

&lt;p&gt;This is how a number is denoted in &lt;strong&gt;IEEE 754&lt;/strong&gt; format:&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.amazonaws.com%2Fuploads%2Farticles%2Fbl82ae9dnomcr51hqvkf.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.amazonaws.com%2Fuploads%2Farticles%2Fbl82ae9dnomcr51hqvkf.png" alt="IEEE 754 notation" width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here &lt;em&gt;s&lt;/em&gt; is the sign bit (0 for positive, 1 for negative), &lt;em&gt;M&lt;/em&gt; is the mantissa (holds the digits of the number) and &lt;em&gt;E&lt;/em&gt; is the exponent which determines the scale of the number.&lt;/p&gt;

&lt;p&gt;You would not be able to find any integer values for M and E that can exactly represent numbers like 0.1, 0.2 or 0.3 in this format. We can only pick values for M and E that give the closest result.&lt;/p&gt;

&lt;p&gt;Here is a tool you could use to determine the &lt;strong&gt;IEEE 754&lt;/strong&gt; notations of decimal numbers: &lt;a href="https://www.h-schmidt.net/FloatConverter/IEEE754.html" rel="noopener noreferrer"&gt;https://www.h-schmidt.net/FloatConverter/IEEE754.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IEEE 754&lt;/strong&gt; notation of 0.25:&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.amazonaws.com%2Fuploads%2Farticles%2Fk1b2jy4mv9awvq1i5x6h.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.amazonaws.com%2Fuploads%2Farticles%2Fk1b2jy4mv9awvq1i5x6h.png" alt="IEEE notation of 0.25" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IEEE 754&lt;/strong&gt; notation of 0.1 and 0.2 respectively:&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.amazonaws.com%2Fuploads%2Farticles%2F7wk2ozigfsygvg4744vz.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.amazonaws.com%2Fuploads%2Farticles%2F7wk2ozigfsygvg4744vz.png" alt="IEEE notation of 0.1" width="800" height="405"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fzqy1tpgiyejcuk9592en.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.amazonaws.com%2Fuploads%2Farticles%2Fzqy1tpgiyejcuk9592en.png" alt="IEEE notation of 0.2" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note that the error due to conversion in case of 0.25 was 0, while 0.1 and 0.2 had non-zero errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IEEE 754&lt;/strong&gt; defines the following formats for representing floating-point numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Single-precision (32-bit): 1 bit for sign, 8 bits for exponent, 23 bits for mantissa&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Double-precision (64-bit): 1 bit for sign, 11 bits for exponent, 52 bits for mantissa&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the sake of simplicity, let us consider the single-precision format that uses 32 bits. &lt;/p&gt;

&lt;p&gt;The 32 bit representation of 0.1 is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 01111011 10011001100110011001101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the first bit represents the sign (0 which means positive in this case), the next 8 bits (01111011) represent the exponent and the final 23 bits (10011001100110011001101) represent the mantissa.&lt;/p&gt;

&lt;p&gt;This is not an exact representation. It represents ≈ &lt;strong&gt;&lt;em&gt;0.100000001490116119384765625&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similarly, the 32 bit representation of 0.2 is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 01111100 10011001100110011001101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not an exact representation either. It represents ≈ &lt;strong&gt;&lt;em&gt;0.20000000298023223876953125&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When added, this results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 01111101 11001101010011001100110 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is  ≈ &lt;strong&gt;&lt;em&gt;0.30000001192092896&lt;/em&gt;&lt;/strong&gt; in decimal representation.&lt;/p&gt;

&lt;p&gt;In conclusion, the seemingly perplexing result of 0.1 + 0.2 not yielding 0.3 is not an anomaly specific to JavaScript, but a consequence of the limitations of floating-point arithmetic across programming languages. The roots of this behaviour lie in the binary representation of numbers, which inherently leads to precision errors when handling certain fractions. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>The CORS Conundrum</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Sun, 11 Feb 2024 02:27:44 +0000</pubDate>
      <link>https://dev.to/umangsinha12/the-cors-conundrum-4k83</link>
      <guid>https://dev.to/umangsinha12/the-cors-conundrum-4k83</guid>
      <description>&lt;p&gt;If you're a back end developer you must have been in a position where the API you wrote worked perfectly fine when tested with Postman, cURL or any other API testing tool but as soon as the frontend application started consuming your API, the following much dreaded error started appearing:&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.amazonaws.com%2Fuploads%2Farticles%2Fjiphuiohlyfl8okcdqqh.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.amazonaws.com%2Fuploads%2Farticles%2Fjiphuiohlyfl8okcdqqh.png" alt="Console error" width="800" height="116"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2F5sze2kkxnne8w78clsyj.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.amazonaws.com%2Fuploads%2Farticles%2F5sze2kkxnne8w78clsyj.png" alt="Network tab" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've been there, this article is for you. &lt;/p&gt;

&lt;p&gt;We will dive deep into CORS and explore what it is, why it is needed and how to deal with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is CORS?
&lt;/h2&gt;

&lt;p&gt;According to MDN Web Docs, "Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources."&lt;/p&gt;

&lt;p&gt;If that didn't make a lot of sense to you, here's a diagram to simplify things:&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.amazonaws.com%2Fuploads%2Farticles%2Fo1hl50js6h8d31ek24ll.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.amazonaws.com%2Fuploads%2Farticles%2Fo1hl50js6h8d31ek24ll.png" alt="Same Origin Requests" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a website hosted at xyz.com sends requests to a web server also hosted at xyz.com (same domain, protocol and port), the request is a 'same-origin request'. These requests are generally allowed and have fewer restrictions. However, they are still subject to security mechanisms such as the Same Origin Policy (SOP).&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.amazonaws.com%2Fuploads%2Farticles%2Fwck05kl02fzhfri9oly9.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.amazonaws.com%2Fuploads%2Farticles%2Fwck05kl02fzhfri9oly9.png" alt="Cross Origin Requests" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a website hosted at xyz.com sends requests to a web server hosted at abc.com, the request is a 'cross-origin request'. By default, web browsers restrict cross-origin requests to prevent unauthorized access to sensitive data or resources. However, there are mechanisms such as Cross-Origin Resource Sharing (CORS) that allow servers to explicitly authorize cross-origin requests from specific origins.&lt;/p&gt;

&lt;p&gt;If the website hosted at xyz.com wants to fetch data from an API hosted at abc.com, the server at abc.com needs to include CORS headers in its response to allow requests from xyz.com.  &lt;/p&gt;

&lt;p&gt;Since the browser is the one who restricts cross-origin resource sharing, the API you built works in Postman, cURL and other API testing tools but not in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do browsers restrict cross-origin resource sharing?
&lt;/h2&gt;

&lt;p&gt;Let us assume the following three scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Person A gets tricked into clicking on a specially crafted link that they received over email or found embedded on a malicious website. Person A was logged into their bank account in the same browser session thereby allowing the malicious request to be executed and transferring funds from A's account to the attacker's account without A's knowledge.&lt;/li&gt;
&lt;li&gt;The attacker crafts a request to change the password of the victim's account on a web service. Person B is then lured into clicking a link embedded in a phishing email or disguised as a legitimate action. If Person B is logged into the targeted web service in the same browser session, the malicious request gets executed, thereby changing the password of the victim's account.&lt;/li&gt;
&lt;li&gt;Person C clicks on a phishing link that they received. Person C was also logged into their email account in the same browser session. The malicious request is thus executed and email forwarding gets configured without Person C's knowledge. All the incoming emails from Person C's email now start being forwarded to the attacker's email.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the above mentioned scenarios are examples of CSRF (Cross-Site Request Forgery) attacks that could have been avoided if the server had CORS configured. Browsers restrict CORS to enforce SOP and mitigate security risks such as unauthorized access and CSRF attacks. By implementing proper CORS policies, web developers can control access to resources on their servers and ensure that sensitive operations are only allowed from trusted origins, thereby enhancing the overall security of their web applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, how are CORS policies implemented on the server-side?
&lt;/h2&gt;

&lt;p&gt;To demonstrate this I will be using a simple server I built using ExpressJS. The server supports three endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/add-student - takes &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; as inputs and adds a new student&lt;/li&gt;
&lt;li&gt;/delete-student - takes &lt;code&gt;id&lt;/code&gt; as input and deletes the student with that &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;/get-student - takes &lt;code&gt;id&lt;/code&gt; as input and fetches the student&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a simple frontend that we will be using to send these API requests. It was made using plain HTML and it looks something like this:&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.amazonaws.com%2Fuploads%2Farticles%2Fj2a5niikutqua6ipw4oy.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.amazonaws.com%2Fuploads%2Farticles%2Fj2a5niikutqua6ipw4oy.png" alt="Client" width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the code for the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Student&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;input type="text" id="textField1" placeholder="Student ID" /&amp;gt;
    &amp;lt;input type="text" id="textField2" placeholder="Student name" /&amp;gt;
    &amp;lt;button onclick="addStudent()"&amp;gt;Add student&amp;lt;/button&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

    &amp;lt;input type="text" id="textField3" placeholder="Student ID to delete" /&amp;gt;
    &amp;lt;button onclick="deleteStudent()"&amp;gt;Delete student&amp;lt;/button&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

    &amp;lt;input type="text" id="textField4" placeholder="Get student" /&amp;gt;
    &amp;lt;button onclick="getStudent()"&amp;gt;Get student&amp;lt;/button&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

    &amp;lt;script&amp;gt;
      let apiUrl = "http://127.0.0.1:3000";

      function addStudent() {
        let studentId = document.getElementById("textField1").value;
        let studentName = document.getElementById("textField2").value;

        fetch(apiUrl + "/add-student", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ name: studentName, id: studentId }),
        }).then((response) =&amp;gt; {
          if (response.status === 200) {
            console.log(response);
          } else {
            const div = document.createElement("div");
            div.innerText = "Something went wrong";
            document.body.appendChild(div);
          }
        });
      }

      function deleteStudent() {
        let idToDelete = document.getElementById("textField3").value;

        fetch(apiUrl + "/delete-student", {
          method: "DELETE",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ id: idToDelete }),
        }).then((response) =&amp;gt; {
          if (response.status === 200) {
            console.log(response);
          } else {
            const div = document.createElement("div");
            div.innerText = "Something went wrong";
            document.body.appendChild(div);
          }
        });
      }

      function getStudent() {
        let idToSearch = document.getElementById("textField4").value;
        fetch(apiUrl + "/get-student" + "?id=" + idToSearch, {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
          },
        }).then((response) =&amp;gt; {
          if (response.status === 200) {
            console.log(response);
          } else {
            const div = document.createElement("div");
            div.innerText = "Something went wrong";
            document.body.appendChild(div);
          }
        });
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require("express");

const app = express();
const port = 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

let students = [];

app.get("/get-student", (req, res) =&amp;gt; {
  const idToSearch = req.query?.id;
  for (let i = 0; i &amp;lt; students.length; i++) {
    if (students[i]["id"] == idToSearch) {
      return res.status(200).send({ student: students[i] });
    }
  }
  return res.status(404).send("Not found");
});

app.post("/add-student", (req, res) =&amp;gt; {
  const id = req.body?.id;
  const name = req.body?.name;
  students.push({ id, name });
  return res.status(200).send({ students, message: "Successfully added" });
});

app.delete("/delete-student", (req, res) =&amp;gt; {
  const idToDelete = req.body?.id;
  for (let i = 0; i &amp;lt; students.length; i++) {
    if (students[i]["id"] == idToDelete) {
      students.splice(i, 1);
      return res.status(200).send({ students });
    }
  }
  return res.status(404).send("Not found");
});

app.listen(port, () =&amp;gt; {
  console.log(`Server listening on port ${port}`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And another simple server that serves the HTML file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require("express");

const app = express();
const port = 4000;

app.get("/", function (req, res) {
  res.sendFile(__dirname + "/index.html");
});

app.listen(port, () =&amp;gt; {
  console.log(`Server listening on port ${port}`);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is listening on port 3000 while the HTML file is being served at port 4000. Thus, any request sent from the client to the server in this case would be a Cross-Origin Request.&lt;/p&gt;

&lt;p&gt;If we now try to add a student with &lt;code&gt;id&lt;/code&gt; 101 and &lt;code&gt;name&lt;/code&gt; Alex we get the much dreaded CORS error as expected: &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.amazonaws.com%2Fuploads%2Farticles%2Fkxm2lny1czmi2tq6huop.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.amazonaws.com%2Fuploads%2Farticles%2Fkxm2lny1czmi2tq6huop.png" alt="CORS Error" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We did not configure CORS on our backend. The browser tried to send a preflight request to the server and did not receive appropriate headers in the response.&lt;/p&gt;

&lt;p&gt;The browser first sends a preflight request to the server to determine if the actual request is safe to send. This preflight request is an HTTP OPTIONS request that includes specific headers, such as &lt;code&gt;Origin&lt;/code&gt;, &lt;code&gt;Access-Control-Request-Method&lt;/code&gt;, and &lt;code&gt;Access-Control-Request-Headers&lt;/code&gt;. The server must respond to the preflight request with appropriate CORS headers indicating whether the actual request is allowed. These headers include &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;, &lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt;, &lt;code&gt;Access-Control-Allow-Headers&lt;/code&gt;, and others.&lt;/p&gt;

&lt;p&gt;Only after the browser receives a satisfactory response to the preflight request will it send the actual request (e.g., GET, POST, etc.). If the preflight request fails or if the server does not respond with the required CORS headers, the browser will block the actual request, preventing potential cross-origin security vulnerabilities.&lt;/p&gt;

&lt;p&gt;Let us now create a middleware that will add the required CORS headers to the API response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// CORS middleware
app.use((req, res, next) =&amp;gt; {
  res.header("Access-Control-Allow-Origin", "http://127.0.0.1:4000");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  // Allow specific methods
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  next();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server code finally looks 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;const express = require("express");

const app = express();
const port = 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

let students = [];

// CORS middleware
app.use((req, res, next) =&amp;gt; {
  res.header("Access-Control-Allow-Origin", "http://127.0.0.1:4000");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  // Allow specific methods
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  next();
});

app.get("/get-student", (req, res) =&amp;gt; {
  const idToSearch = req.query?.id;
  for (let i = 0; i &amp;lt; students.length; i++) {
    if (students[i]["id"] == idToSearch) {
      return res.status(200).send({ student: students[i] });
    }
  }
  return res.status(404).send("Not found");
});

app.post("/add-student", (req, res) =&amp;gt; {
  const id = req.body?.id;
  const name = req.body?.name;
  students.push({ id, name });
  return res.status(200).send({ students, message: "Successfully added" });
});

app.delete("/delete-student", (req, res) =&amp;gt; {
  const idToDelete = req.body?.id;
  for (let i = 0; i &amp;lt; students.length; i++) {
    if (students[i]["id"] == idToDelete) {
      students.splice(i, 1);
      return res.status(200).send({ students });
    }
  }
  return res.status(404).send("Not found");
});

app.listen(port, () =&amp;gt; {
  console.log(`Server listening on port ${port}`);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the server and try adding the student again. Now it works!&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.amazonaws.com%2Fuploads%2Farticles%2F85u764o4vzxxpgwwgqx6.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.amazonaws.com%2Fuploads%2Farticles%2F85u764o4vzxxpgwwgqx6.png" alt="CORS Working" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The preflight request received a response with all the required CORS headers this time and thus the browser sent the actual request.&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.amazonaws.com%2Fuploads%2Farticles%2Fqlyhcbjb1kc28xhwjmem.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.amazonaws.com%2Fuploads%2Farticles%2Fqlyhcbjb1kc28xhwjmem.png" alt="Preflight headers" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The CORS Headers
&lt;/h2&gt;

&lt;p&gt;In the middleware, we set the following three headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access-Control-Allow-Origin&lt;/li&gt;
&lt;li&gt;Access-Control-Allow-Headers&lt;/li&gt;
&lt;li&gt;Access-Control-Allow-Methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let us look at each of these headers in detail:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access-Control-Allow-Origin:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the server includes &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; in the response header, it allows requests from any origin. This is not a good practice generally and is considered a security risk unless you are intentionally building a public API that should be accessible from any origin. It effectively disables the Same-Origin Policy, which is designed to protect against attacks, such as Cross-Site Request Forgery (CSRF).&lt;br&gt;
If the server includes &lt;code&gt;Access-Control-Allow-Origin: &amp;lt;origin&amp;gt;&lt;/code&gt; in the response header, it allows requests only from the specified origin (it was set to &lt;a href="http://127.0.0.1:4000" rel="noopener noreferrer"&gt;http://127.0.0.1:4000&lt;/a&gt; in the above example).&lt;br&gt;
If the server does not include the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header in the response (or includes it with a different origin), the browser will block the request due to the Same-Origin Policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access-Control-Allow-Headers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Along with the preflight request, the browser includes the &lt;code&gt;Access-Control-Request-Headers&lt;/code&gt; header, which lists the headers that the client wants to include in the actual request.&lt;br&gt;
The server then responds to the preflight request, and if it allows the requested headers, it includes the &lt;code&gt;Access-Control-Allow-Headers&lt;/code&gt; header in the response. This header contains a comma-separated list of the headers that the server allows (we set this header to "Origin, X-Requested-With, Content-Type, Accept" in the above example).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access-Control-Allow-Methods:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the preflight request, the browser includes the &lt;code&gt;Access-Control-Request-Method&lt;/code&gt; header, which specifies the method that the client wants to use in the actual request. The server then responds to the preflight request, and if it allows the requested method, it includes the &lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt; header in the response. This header contains a comma-separated list of the HTTP methods that the server allows (we set this header to "GET, POST, PUT, DELETE, OPTIONS" in the above example)&lt;/p&gt;

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

&lt;p&gt;If you feel this dive into CORS wasn't deep enough or want to explore further, MDN Web Docs would be the best place to continue reading: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors" rel="noopener noreferrer"&gt;MDN Web Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code used to demonstrate CORS in this article can be found here: &lt;a href="https://github.com/umang-sinha/cors-demo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this article helped you understand what CORS is and how you can deal with it the next time you see "CORS Error" flashing on your screen!&lt;/p&gt;

</description>
      <category>backenddevelopment</category>
      <category>network</category>
      <category>security</category>
      <category>api</category>
    </item>
    <item>
      <title>How to fetch data from REST APIs in Flutter? 💻</title>
      <dc:creator>Umang Sinha</dc:creator>
      <pubDate>Sat, 19 Jun 2021 20:06:08 +0000</pubDate>
      <link>https://dev.to/umangsinha12/fetch-data-from-rest-apis-in-flutter-2ec6</link>
      <guid>https://dev.to/umangsinha12/fetch-data-from-rest-apis-in-flutter-2ec6</guid>
      <description>&lt;p&gt;I remember being stuck with REST APIs when I was new to Flutter and programming in general. As a beginner I didn't know where to find the solutions to my problem. I was often advised to read the official documentation but those docs always looked very intimidating to me as a complete beginner. That is when I stumbled upon this beautiful community of people on the internet that are always ready to help out. After having gained so much I guess it's time to give back to this gorgeous community and that is why I am writing my first ever blog post 🤩&lt;/p&gt;

&lt;p&gt;In this article, we'll try to fetch some dummy data from a REST API hosted by  &lt;a href="https://reqres.in/" rel="noopener noreferrer"&gt;Reqres.in&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we begin, add the following code into the &lt;code&gt;main.dart&lt;/code&gt; file of your flutter app:&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.amazonaws.com%2Fuploads%2Farticles%2F85pkq5magj2c4emgmgb2.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.amazonaws.com%2Fuploads%2Farticles%2F85pkq5magj2c4emgmgb2.png" alt="code" width="800" height="956"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you are done with this, your app should look something like this:&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.amazonaws.com%2Fuploads%2Farticles%2Flb64cyi6qg68jvv8xqzp.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Flb64cyi6qg68jvv8xqzp.jpg" alt="Screenshot" width="800" height="1733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we can make our first HTTP request we need to install some packages. You can now head over to &lt;a href="https://pub.dev/" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; and search for 'http'. The package that we are looking for is &lt;a href="https://pub.dev/packages/http/install" rel="noopener noreferrer"&gt;this.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to install this package you can follow the below mentioned steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the following command in your terminal:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ flutter pub add http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;After you have done this your IDE will run the &lt;code&gt;flutter pub get&lt;/code&gt; command. In case it doesn't, you can manually do it by typing it into your terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;http&lt;/code&gt; package has now been installed. In order to access it, we can import it as a library by adding the following line of code to the top of our &lt;code&gt;main.dart&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:http/http.dart' as http;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that everything is setup, we can start accessing the &lt;code&gt;http&lt;/code&gt; library and use it to send HTTP requests to the REST API. Let's get coding! 🚀&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.amazonaws.com%2Fuploads%2Farticles%2F66is671ieuohklkgc1k8.gif" 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.amazonaws.com%2Fuploads%2Farticles%2F66is671ieuohklkgc1k8.gif" alt="typing cat gif" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our next step would be to create a function that will fetch the data from the REST API and print it to the console. In order to keep things simple, let us name the function &lt;code&gt;getData()&lt;/code&gt; which is exactly what it does - it gets data! This function will be an asynchronous function and will have the return type of &lt;code&gt;Future&amp;lt;String&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wait what? What the heck is Future? Shouldn't the return type be just &lt;code&gt;String&lt;/code&gt;?&lt;/em&gt; &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.amazonaws.com%2Fuploads%2Farticles%2Fxex2oyyrs4scl11uvw8x.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fxex2oyyrs4scl11uvw8x.jpg" alt="confused guy" width="600" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does that look weird to you? Don't worry! 🤝 I felt the same when I saw it for the first time. Let us try to understand it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Future&amp;lt;String&amp;gt;&lt;/code&gt; can be thought of as a promise token that doesn't have any data right now but promises to provide a String in the future. A Future can have two possible states: &lt;em&gt;Uncompleted&lt;/em&gt; and &lt;em&gt;Complete&lt;/em&gt;. The Future is in the &lt;em&gt;Uncompleted&lt;/em&gt; state when it doesn't yet have the data that it promised to provide.&lt;/p&gt;

&lt;p&gt;Inside the function, let us declare a &lt;code&gt;final&lt;/code&gt; variable that stores the URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final url = Uri.parse('https://reqres.in/api/users?page=2');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now send an HTTP request to the REST API with the help of the &lt;code&gt;get()&lt;/code&gt; method that the &lt;code&gt;http&lt;/code&gt; package offers and store it in a variable called &lt;code&gt;response&lt;/code&gt;. This response will be of the type &lt;code&gt;http.Response&lt;/code&gt;. We also specify in the header of our request that we want to receive a response in the JSON format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http.Response response = await http.get(
      url,
      headers: {
        'Accept' : 'application/json'
      });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now print the response body to the console!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(response.body);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getData&lt;/code&gt; function should finally look like this:&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.amazonaws.com%2Fuploads%2Farticles%2F0l8qesaoo1w4dzbnyfdu.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.amazonaws.com%2Fuploads%2Farticles%2F0l8qesaoo1w4dzbnyfdu.png" alt="code" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There you go! We should now be able to receive data from the REST API and print it to the console. Just pass the &lt;code&gt;getData&lt;/code&gt; function to the onPressed listener of the button and reload your app.&lt;/p&gt;

&lt;p&gt;Now press the button on your app and voila! 🥳 Your console will print out some data from the REST API like this:&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.amazonaws.com%2Fuploads%2Farticles%2Ftxi2vhxjfl0xq8kyhtt6.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Ftxi2vhxjfl0xq8kyhtt6.jpg" alt="console" width="800" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's connect 👇&lt;br&gt;
&lt;a href="https://github.com/umang-sinha" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/umang-sinha" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
