DEV Community

Vishnu B
Vishnu B

Posted on Fully Autonomous

How a HashMap Works: A Quick Walkthrough with Java

You probably used a HashMap today without thinking about it. But what actually happens when you call map.put("apple", 3)? Let's open the box, with plain words and a small Java program you can run yourself.

The idea in one minute

A HashMap is an array of buckets. When you store something:

  1. The key goes through hashCode(), which turns it into a number.
  2. That number is reduced to a bucket index (in our model: hash % 8).
  3. The key and value are stored in that bucket.

Two different keys can land on the same bucket. That is a collision, and the bucket simply keeps them as a small chain. Finding a value means: hash the key, jump to the bucket, walk the short chain comparing keys.

Watch the flow

Animation: a key is hashed to a bucket, a colliding key joins the bucket chain, and a lookup walks the chain

The animation walks three moments: storing apple in an empty bucket, storing mango which collides with apple and joins its chain, then finding apple again by walking that chain. (It shows a simplified 8-bucket teaching model - the same one the code below runs.)

Run it yourself

This tiny hash map prints every step, so you can see the hashing, the bucket choice, the collision and the chain walk:

import java.util.HashMap;

public class HashMapDemo {

    // A tiny hand-rolled hash map so we can watch every step
    static class SimpleHashMap {
        static final int BUCKETS = 8;
        static class Node {
            String key; int value; Node next;
            Node(String k, int v) { key = k; value = v; }
        }
        Node[] table = new Node[BUCKETS];

        int bucketIndex(String key) {
            return (key.hashCode() & 0x7fffffff) % BUCKETS;
        }

        void put(String key, int value) {
            int i = bucketIndex(key);
            for (Node n = table[i]; n != null; n = n.next) {
                if (n.key.equals(key)) {
                    System.out.println("put(" + key + ", " + value + ") -> bucket " + i + ": key exists, value overwritten");
                    n.value = value;
                    return;
                }
            }
            Node n = new Node(key, value);
            n.next = table[i];
            table[i] = n;
            System.out.println("put(" + key + ", " + value + ") -> hash " + (key.hashCode() & 0x7fffffff)
                + " -> bucket " + i + (n.next != null ? " (collision: added to chain)" : ""));
        }

        Integer get(String key) {
            int i = bucketIndex(key);
            int steps = 0;
            for (Node n = table[i]; n != null; n = n.next) {
                steps++;
                if (n.key.equals(key)) {
                    System.out.println("get(" + key + ") -> bucket " + i + ", found after " + steps + " step(s): " + n.value);
                    return n.value;
                }
            }
            System.out.println("get(" + key + ") -> bucket " + i + ": not in chain, returns null");
            return null;
        }

        void printTable() {
            System.out.println("\nFinal table (" + BUCKETS + " buckets):");
            for (int i = 0; i < BUCKETS; i++) {
                StringBuilder sb = new StringBuilder("bucket " + i + ": ");
                if (table[i] == null) sb.append("(empty)");
                for (Node n = table[i]; n != null; n = n.next) sb.append(n.key).append("=").append(n.value).append(" -> ");
                if (sb.toString().endsWith(" -> ")) sb.setLength(sb.length() - 4);
                System.out.println(sb);
            }
        }
    }

    public static void main(String[] args) {
        SimpleHashMap map = new SimpleHashMap();
        map.put("cat", 1);
        map.put("dog", 2);
        map.put("apple", 3);
        map.put("banana", 4);
        map.put("mango", 5);
        map.put("cat", 9);   // same key again: overwrite
        map.printTable();
        System.out.println();
        map.get("dog");
        map.get("apple");
        map.get("zebra");

        System.out.println("\n-- The real java.util.HashMap --");
        HashMap<String, Integer> real = new HashMap<>();
        real.put("cat", 1);
        real.put("dog", 2);
        real.put("apple", 3);
        real.put(null, 99);           // null key is allowed
        System.out.println("real.get(\"cat\") = " + real.get("cat"));
        System.out.println("real.get(null) = " + real.get(null));
        System.out.println("iteration order: " + real.keySet() + "  (not guaranteed, do not rely on it)");
    }
}
Enter fullscreen mode Exit fullscreen mode

What it actually printed (JDK 21)

put(cat, 1) -> hash 98262 -> bucket 6
put(dog, 2) -> hash 99644 -> bucket 4
put(apple, 3) -> hash 93029210 -> bucket 2
put(banana, 4) -> hash 751128421 -> bucket 5
put(mango, 5) -> hash 103662530 -> bucket 2 (collision: added to chain)
put(cat, 9) -> bucket 6: key exists, value overwritten

Final table (8 buckets):
bucket 0: (empty)
bucket 1: (empty)
bucket 2: mango=5 -> apple=3
bucket 3: (empty)
bucket 4: dog=2
bucket 5: banana=4
bucket 6: cat=9
bucket 7: (empty)

get(dog) -> bucket 4, found after 1 step(s): 2
get(apple) -> bucket 2, found after 2 step(s): 3
get(zebra) -> bucket 6: not in chain, returns null

-- The real java.util.HashMap --
real.get("cat") = 1
real.get(null) = 99
iteration order: [null, apple, cat, dog]  (not guaranteed, do not rely on it)
Enter fullscreen mode Exit fullscreen mode

Three things to notice:

  • mango and apple both hash to bucket 2, so they sit in one chain. get("apple") checks mango first, then finds apple on step 2.
  • Storing cat again does not add a second entry; it overwrites the value (1 becomes 9).
  • zebra hashes to bucket 6, which holds cat. The map walks that chain, finds no match, and returns null - a wrong bucket is never even looked at.

The real HashMap adds a few upgrades

Our 8-bucket model keeps the moving parts visible. Per the Java 21 HashMap documentation, the real one:

  • Gives constant-time get and put, assuming the hash function spreads keys well across buckets.
  • Grows automatically: when entries exceed capacity x load factor (default 0.75), it rehashes to roughly twice the buckets.
  • Permits one null key and any number of null values.
  • Makes no order guarantees - do not rely on iteration order.
  • Is not synchronized; for concurrent access wrap it with Collections.synchronizedMap (or use a concurrent map).
  • Can use comparison order to break ties when many keys share a hash code (in practice, very long chains are turned into trees to keep lookups fast).

Sources

Written with AI assistance; code examples were compiled and run on JDK 21 before publishing.

Top comments (0)