<?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: Gaurav Tyagi</title>
    <description>The latest articles on DEV Community by Gaurav Tyagi (@gaurav_tyagi_4d2a33837e04).</description>
    <link>https://dev.to/gaurav_tyagi_4d2a33837e04</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2345212%2Fc8de9e9b-c599-443a-88fb-c94966ae03ea.jpg</url>
      <title>DEV Community: Gaurav Tyagi</title>
      <link>https://dev.to/gaurav_tyagi_4d2a33837e04</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gaurav_tyagi_4d2a33837e04"/>
    <language>en</language>
    <item>
      <title>I Built My Own Fail-Fast HashMap — Here's Why a Boolean Flag Wasn't Enough</title>
      <dc:creator>Gaurav Tyagi</dc:creator>
      <pubDate>Sun, 30 Aug 2026 20:45:30 +0000</pubDate>
      <link>https://dev.to/gaurav_tyagi_4d2a33837e04/i-built-my-own-fail-fast-hashmap-heres-why-a-boolean-flag-wasnt-enough-5aa9</link>
      <guid>https://dev.to/gaurav_tyagi_4d2a33837e04/i-built-my-own-fail-fast-hashmap-heres-why-a-boolean-flag-wasnt-enough-5aa9</guid>
      <description>&lt;p&gt;If you've done LeetCode's &lt;a href="https://leetcode.com/problems/design-hashmap/" rel="noopener noreferrer"&gt;Design HashMap&lt;/a&gt;, you've implemented &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, and &lt;code&gt;remove&lt;/code&gt;. What that exercise usually skips is the part that actually breaks in production: what happens when someone mutates the map &lt;em&gt;while&lt;/em&gt; another piece of code is iterating over it.&lt;/p&gt;

&lt;p&gt;I ran into this directly while building &lt;code&gt;MyHashMap&lt;/code&gt;, a from-scratch single-threaded HashMap (separate chaining, resize on load factor). Getting &lt;code&gt;put&lt;/code&gt;/&lt;code&gt;get&lt;/code&gt;/&lt;code&gt;remove&lt;/code&gt; right was the easy 80%. Getting &lt;code&gt;entrySet().iterator()&lt;/code&gt; to correctly detect concurrent mutation — including the case where a &lt;em&gt;second, completely separate&lt;/em&gt; iterator is the one that should notice — took three wrong turns before landing on the pattern the JDK actually uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, concretely
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;V&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entrySet&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;someNewKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;someValue&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// structural change, mid-iteration&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ??? — undefined behavior if we don't guard against this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a guard, &lt;code&gt;next()&lt;/code&gt; might return a stale entry, skip entries entirely, or throw an unrelated exception depending on internal bucket-array state. Java's real collections handle this with &lt;code&gt;ConcurrentModificationException&lt;/code&gt; (CME) — but the interesting part isn't the exception, it's the mechanism that detects when to throw it.&lt;/p&gt;

&lt;h2&gt;
  
  
  First idea: a boolean "dirty" flag
&lt;/h2&gt;

&lt;p&gt;Obvious first attempt: a &lt;code&gt;boolean modified&lt;/code&gt; field on the map, flipped to &lt;code&gt;true&lt;/code&gt; on any &lt;code&gt;put&lt;/code&gt;/&lt;code&gt;remove&lt;/code&gt;, checked by the iterator. This works for exactly one iterator. It falls apart the moment two iterators are alive at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Iterator A calls &lt;code&gt;next()&lt;/code&gt;, sees &lt;code&gt;modified == false&lt;/code&gt;, proceeds.&lt;/li&gt;
&lt;li&gt;Something else mutates the map. &lt;code&gt;modified&lt;/code&gt; flips to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Iterator B — created &lt;em&gt;after&lt;/em&gt; that mutation — checks the &lt;em&gt;same shared&lt;/em&gt; &lt;code&gt;modified&lt;/code&gt; flag, sees &lt;code&gt;true&lt;/code&gt;, and incorrectly throws, even though nothing has changed since B was created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single shared boolean can't represent "changed since &lt;em&gt;this specific iterator&lt;/em&gt; was created" for more than one iterator at a time. Resetting it on read doesn't help either — now the &lt;em&gt;other&lt;/em&gt; iterator stops seeing the change it legitimately needed to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second idea: a timestamp
&lt;/h2&gt;

&lt;p&gt;Next instinct: give the map a &lt;code&gt;lastModified&lt;/code&gt; timestamp instead, and have each iterator capture the current time on creation. Compare timestamps instead of a shared flag — now each iterator has its own baseline.&lt;/p&gt;

&lt;p&gt;This closes the multi-iterator gap, but introduces a different bug: &lt;strong&gt;resolution&lt;/strong&gt;. Two mutations in a tight loop, or a mutation immediately followed by iterator creation, can land in the same millisecond (&lt;code&gt;System.currentTimeMillis()&lt;/code&gt;) — or even the same tick of &lt;code&gt;System.nanoTime()&lt;/code&gt; on some platforms. If a real modification and an iterator's baseline capture ever produce the &lt;em&gt;same&lt;/em&gt; timestamp value, the comparison can't tell who happened first. Worse, &lt;code&gt;currentTimeMillis()&lt;/code&gt; isn't even guaranteed monotonic — it can jump backward on a clock adjustment.&lt;/p&gt;

&lt;p&gt;The deeper issue: this isn't a timing problem. It's a "did &lt;em&gt;anything&lt;/em&gt; change since I looked" problem, and wall-clock time is the wrong tool for a question that has nothing to do with elapsed time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually works: a monotonic counter (modCount)
&lt;/h2&gt;

&lt;p&gt;Every mutation increments a plain &lt;code&gt;long&lt;/code&gt; counter. Every iterator, at creation, snapshots the counter's current value. Every &lt;code&gt;next()&lt;/code&gt; call compares its snapshot against the live value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyEntryIterator&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;V&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;expectedVersion&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyEntryIterator&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expectedVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// snapshot at creation&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;V&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedVersion&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConcurrentModificationException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ... advance and return&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No shared mutable flag, no clock. Each iterator carries its &lt;em&gt;own&lt;/em&gt; independent baseline (&lt;code&gt;expectedVersion&lt;/code&gt;), so the multi-iterator case that broke the boolean flag now works automatically — iterator B's snapshot is whatever the counter was when &lt;em&gt;B&lt;/em&gt; was created, completely independent of A's. And since it's a plain increment, not a physical measurement, there's zero resolution/collision risk: every mutation gets a value strictly different from every other, no matter how fast they happen. This is exactly the pattern &lt;code&gt;java.util.HashMap&lt;/code&gt;, &lt;code&gt;ArrayList&lt;/code&gt;, and friends use internally — go read &lt;code&gt;AbstractList&lt;/code&gt;'s &lt;code&gt;modCount&lt;/code&gt; field and the &lt;code&gt;ConcurrentModificationException&lt;/code&gt; javadoc directly; it's short.&lt;/p&gt;

&lt;h2&gt;
  
  
  The subtlety that actually caught me: no "free" first call
&lt;/h2&gt;

&lt;p&gt;Here's the case that exposed a real bug in my own test suite, not just the implementation. Two iterators, A and B, created back to back — &lt;em&gt;before&lt;/em&gt; either has called &lt;code&gt;next()&lt;/code&gt; even once:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant Map
    participant IteratorA
    participant IteratorB

    Note over Map: version = 3 (after 3 puts)
    IteratorA-&amp;gt;&amp;gt;Map: create (snapshot version=3)
    IteratorB-&amp;gt;&amp;gt;Map: create (snapshot version=3)

    IteratorA-&amp;gt;&amp;gt;Map: next()
    IteratorA-&amp;gt;&amp;gt;Map: remove() → version = 4

    IteratorB-&amp;gt;&amp;gt;Map: next()
    Map--&amp;gt;&amp;gt;IteratorB: ConcurrentModificationException
    Note over IteratorB: B never called next() before A's&amp;lt;br/&amp;gt;change — still invalidated on its&amp;lt;br/&amp;gt;very first call&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;I originally wrote a test that let iterator B succeed on its first &lt;code&gt;next()&lt;/code&gt; call before asserting a &lt;em&gt;second&lt;/em&gt; call would throw. That's wrong: B's snapshot predates A's &lt;code&gt;remove()&lt;/code&gt;, so B is already stale the instant A mutates — there's no "one free call" grace period. The check only cares whether a modification happened since the snapshot was taken, not whether the iterator has navigated yet. Fixing that test (not the implementation) was the actual bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The self-invalidation trap
&lt;/h2&gt;

&lt;p&gt;One more sharp edge: an iterator's &lt;em&gt;own&lt;/em&gt; &lt;code&gt;remove()&lt;/code&gt; legitimately bumps the shared counter — so if the iterator doesn't resync its own &lt;code&gt;expectedVersion&lt;/code&gt; immediately afterward, it trips its own check on the very next call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;nextCalled&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;MyEntry&lt;/span&gt; &lt;span class="n"&gt;entryToDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentEntry&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;MyHashMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryToDelete&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expectedVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// resync — or self-CME on the next call&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One more nuance most write-ups skip: not every write is "structural"
&lt;/h2&gt;

&lt;p&gt;The counter shouldn't bump on &lt;em&gt;every&lt;/em&gt; &lt;code&gt;put()&lt;/code&gt; — only on ones that actually change the map's shape. Overwriting the value of an already-present key isn't structural (same key, same position, same size) and doesn't need to invalidate a live iterator; inserting a genuinely new key is, and does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyEntry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// new key: structural&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// walk the chain...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matchFound&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// existing key, value-only: NOT structural, no bump&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches &lt;code&gt;java.util.HashMap&lt;/code&gt;'s real behavior, and it's the kind of detail that only shows up once you've actually built the thing rather than read about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Fail-fast iteration looks like a one-line trick (&lt;code&gt;modCount&lt;/code&gt;) until you actually have to defend it against a second iterator, a same-millisecond race, or your own iterator's legal mutation. Building it from scratch — and writing tests that actually exercise the multi-iterator case — surfaced three separate designs before landing on the one that's actually in the JDK, plus a bug in the &lt;em&gt;test&lt;/em&gt; for the final design, not the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator" rel="noopener noreferrer"&gt;Fail-Safe Iterator vs Fail-Fast Iterator — Baeldung&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/j2objc/javadoc/jre/reference/java/util/ConcurrentModificationException" rel="noopener noreferrer"&gt;&lt;code&gt;ConcurrentModificationException&lt;/code&gt; javadoc&lt;/a&gt; — the "best-effort, not a guarantee" language is worth reading verbatim&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mtumilowicz/java-iterator-fail-types" rel="noopener noreferrer"&gt;java-iterator-fail-types — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
