<?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: Anant Ghadi</title>
    <description>The latest articles on DEV Community by Anant Ghadi (@anantghadi).</description>
    <link>https://dev.to/anantghadi</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%2F4219166%2F6841d0c0-613b-4211-bbfe-2f00ab4e9973.png</url>
      <title>DEV Community: Anant Ghadi</title>
      <link>https://dev.to/anantghadi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anantghadi"/>
    <language>en</language>
    <item>
      <title>How HashMap Really Works in Java 21: 7 Interview Follow-ups Most People Miss</title>
      <dc:creator>Anant Ghadi</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:35:22 +0000</pubDate>
      <link>https://dev.to/anantghadi/how-hashmap-really-works-in-java-21-7-interview-follow-ups-most-people-miss-45j4</link>
      <guid>https://dev.to/anantghadi/how-hashmap-really-works-in-java-21-7-interview-follow-ups-most-people-miss-45j4</guid>
      <description>&lt;p&gt;"How does HashMap work internally?" is probably the most asked Java interview question. Most candidates say "it uses hashing and buckets" and stop there. The interviewer then asks a follow-up, and that is where the round is won or lost.&lt;/p&gt;

&lt;p&gt;Here are the 7 follow-ups I see most often, with answers you can say out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What actually happens when you call put()?
&lt;/h2&gt;

&lt;p&gt;HashMap keeps an array of buckets (a &lt;code&gt;Node&amp;lt;K,V&amp;gt;[] table&lt;/code&gt;). On &lt;code&gt;put(key, value)&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It calls &lt;code&gt;key.hashCode()&lt;/code&gt; and spreads the bits: &lt;code&gt;hash = h ^ (h &amp;gt;&amp;gt;&amp;gt; 16)&lt;/code&gt;. This mixes the high bits into the low bits so small tables still get a good spread.&lt;/li&gt;
&lt;li&gt;It finds the bucket with &lt;code&gt;index = (n - 1) &amp;amp; hash&lt;/code&gt;, where n is the table size (always a power of two).&lt;/li&gt;
&lt;li&gt;If the bucket is empty, the new node goes there. If not, it walks the bucket and uses &lt;code&gt;equals()&lt;/code&gt; to check whether the key already exists. If it does, the value is replaced. If not, the node is added at the end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One-line answer: &lt;strong&gt;hashCode() picks the bucket, equals() picks the entry.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What are the default capacity and load factor, and why do they matter?
&lt;/h2&gt;

&lt;p&gt;Default capacity is 16 and the load factor is 0.75, so the map resizes once it holds more than 12 entries. Resizing doubles the table and redistributes the entries, which costs time. If you know you will store 10,000 entries, size the map up front.&lt;/p&gt;

&lt;p&gt;Since Java 19 there is a cleaner way:&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="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHashMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sizes the table for 10,000 mappings with the load factor already accounted for. Most candidates have never heard of it, so mentioning it stands out.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. How does resizing work without recalculating every hash?
&lt;/h2&gt;

&lt;p&gt;Because the table size is a power of two, doubling it adds one more bit to the index. Java 8+ checks &lt;code&gt;(hash &amp;amp; oldCapacity)&lt;/code&gt;. If that bit is 0, the entry stays at the same index. If it is 1, it moves to &lt;code&gt;index + oldCapacity&lt;/code&gt;. Each bucket splits into a "low" and a "high" list, and the original order is kept.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What happens when many keys collide in one bucket?
&lt;/h2&gt;

&lt;p&gt;Up to Java 7, a bucket was always a linked list, so a bad &lt;code&gt;hashCode()&lt;/code&gt; could turn lookups into O(n).&lt;/p&gt;

&lt;p&gt;Since Java 8, when a single bucket reaches 8 entries and the table has at least 64 buckets, that bucket is converted into a red-black tree, making worst-case lookups O(log n). If the table is smaller than 64, HashMap resizes instead. When a tree shrinks to 6 entries, it turns back into a list.&lt;/p&gt;

&lt;p&gt;Follow-up trap: trees work best when keys implement &lt;code&gt;Comparable&lt;/code&gt;. Without it, HashMap falls back to tie-breaking rules and loses some of the benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why must equals() and hashCode() be overridden together?
&lt;/h2&gt;

&lt;p&gt;The contract: if &lt;code&gt;a.equals(b)&lt;/code&gt; is true, then &lt;code&gt;a.hashCode() == b.hashCode()&lt;/code&gt; must be true.&lt;/p&gt;

&lt;p&gt;Override &lt;code&gt;equals()&lt;/code&gt; but not &lt;code&gt;hashCode()&lt;/code&gt;, and two "equal" keys can land in different buckets. You put a value in, then &lt;code&gt;get(sameLookingKey)&lt;/code&gt; returns null.&lt;/p&gt;

&lt;p&gt;The modern answer: use a record. Records generate &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; from their components automatically.&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;record&lt;/span&gt; &lt;span class="nf"&gt;EmployeeKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&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;
  
  
  6. Why are mutable keys dangerous?
&lt;/h2&gt;

&lt;p&gt;If you change a field used in &lt;code&gt;hashCode()&lt;/code&gt; after inserting the key, the entry is still sitting in the old bucket, but lookups now go to a different one. The value is effectively lost: &lt;code&gt;get()&lt;/code&gt; returns null, and the stale entry stays in the map.&lt;/p&gt;

&lt;p&gt;Rule of thumb: keys should be immutable. &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt; and records are ideal.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Is HashMap thread-safe? What should you use instead?
&lt;/h2&gt;

&lt;p&gt;No. Concurrent writes can lose updates or corrupt the structure. In Java 7, concurrent resizing could even create a loop in a bucket, causing an infinite loop on &lt;code&gt;get()&lt;/code&gt;, because entries were inserted at the head of the list. Java 8 switched to tail insertion, but HashMap is still not safe for concurrent use.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;ConcurrentHashMap&lt;/code&gt;. It locks at the bucket level and uses CAS for empty buckets, so reads are mostly lock-free.&lt;/p&gt;

&lt;p&gt;Follow-up trap: &lt;code&gt;ConcurrentHashMap&lt;/code&gt; does not allow null keys or null values, while HashMap allows one null key (stored in bucket 0) and many null values. The reason: in a concurrent map, &lt;code&gt;get()&lt;/code&gt; returning null would be ambiguous. Is the key missing, or is the value null?&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: what about ordering?
&lt;/h2&gt;

&lt;p&gt;HashMap gives no ordering guarantee. Use &lt;code&gt;LinkedHashMap&lt;/code&gt; for insertion order (or access order, for LRU caches) and &lt;code&gt;TreeMap&lt;/code&gt; for sorted keys. In Java 21, &lt;code&gt;LinkedHashMap&lt;/code&gt; implements the new &lt;code&gt;SequencedMap&lt;/code&gt; interface, so you get &lt;code&gt;firstEntry()&lt;/code&gt;, &lt;code&gt;lastEntry()&lt;/code&gt; and &lt;code&gt;reversed()&lt;/code&gt; directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick revision checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;hashCode() picks the bucket, equals() picks the entry&lt;/li&gt;
&lt;li&gt;Capacity 16, load factor 0.75, resize past 12 entries&lt;/li&gt;
&lt;li&gt;Treeify at 8 entries per bucket when the table has 64+ buckets&lt;/li&gt;
&lt;li&gt;Resize splits buckets using &lt;code&gt;hash &amp;amp; oldCapacity&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Immutable keys, ideally records&lt;/li&gt;
&lt;li&gt;ConcurrentHashMap for concurrency, and no nulls there&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HashMap.newHashMap(n)&lt;/code&gt; to pre-size (Java 19+)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're preparing for Java backend interviews, I put together a guide with 240 questions like this one, covering Core Java, Spring Boot 3, JPA, microservices and system design. There's a free 8-page sample on my profile.&lt;/p&gt;

&lt;p&gt;Which HashMap follow-up caught you off guard in an interview? Tell me in the comments.&lt;/p&gt;

</description>
      <category>java</category>
      <category>interview</category>
      <category>programming</category>
      <category>career</category>
    </item>
  </channel>
</rss>
