Java
HashMap
Data Structures
Java Programming
Coding Tutorial

How to Create Own HashMap in Java?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Writing your own hash map is one of the best ways to understand how a core data structure really works. Java's built-in HashMap is far more advanced than a teaching example, but the same basic ideas still apply: compute a hash, map it to a bucket, store entries there, and deal with collisions when multiple keys land in the same bucket.

A small custom implementation does not need to support every feature of java.util.HashMap to be useful. If it correctly implements put, get, and remove, and if it resizes when buckets get crowded, you already understand the essential mechanics.

The Core Pieces

A simple hash map usually has these parts:

  • an array of buckets
  • an Entry node to hold key, value, and next pointer
  • a hash-to-index calculation
  • collision handling, often by linked-list chaining
  • resize logic when load gets too high

Here is a compact implementation:

java
1import java.util.Objects;
2
3public class MyHashMap<K, V> {
4    private static class Entry<K, V> {
5        final K key;
6        V value;
7        Entry<K, V> next;
8
9        Entry(K key, V value, Entry<K, V> next) {
10            this.key = key;
11            this.value = value;
12            this.next = next;
13        }
14    }
15
16    private Entry<K, V>[] buckets;
17    private int size;
18    private final double loadFactor;
19
20    @SuppressWarnings("unchecked")
21    public MyHashMap(int capacity, double loadFactor) {
22        this.buckets = (Entry<K, V>[]) new Entry[capacity];
23        this.loadFactor = loadFactor;
24    }
25
26    public MyHashMap() {
27        this(16, 0.75);
28    }
29
30    private int indexFor(K key, int length) {
31        int hash = Objects.hashCode(key);
32        return (hash & 0x7fffffff) % length;
33    }
34
35    public V get(K key) {
36        int index = indexFor(key, buckets.length);
37        Entry<K, V> current = buckets[index];
38
39        while (current != null) {
40            if (Objects.equals(current.key, key)) {
41                return current.value;
42            }
43            current = current.next;
44        }
45
46        return null;
47    }
48
49    public void put(K key, V value) {
50        int index = indexFor(key, buckets.length);
51        Entry<K, V> current = buckets[index];
52
53        while (current != null) {
54            if (Objects.equals(current.key, key)) {
55                current.value = value;
56                return;
57            }
58            current = current.next;
59        }
60
61        buckets[index] = new Entry<>(key, value, buckets[index]);
62        size++;
63
64        if ((double) size / buckets.length > loadFactor) {
65            resize();
66        }
67    }
68
69    public V remove(K key) {
70        int index = indexFor(key, buckets.length);
71        Entry<K, V> current = buckets[index];
72        Entry<K, V> previous = null;
73
74        while (current != null) {
75            if (Objects.equals(current.key, key)) {
76                if (previous == null) {
77                    buckets[index] = current.next;
78                } else {
79                    previous.next = current.next;
80                }
81                size--;
82                return current.value;
83            }
84            previous = current;
85            current = current.next;
86        }
87
88        return null;
89    }
90
91    public int size() {
92        return size;
93    }
94
95    @SuppressWarnings("unchecked")
96    private void resize() {
97        Entry<K, V>[] oldBuckets = buckets;
98        buckets = (Entry<K, V>[]) new Entry[oldBuckets.length * 2];
99        size = 0;
100
101        for (Entry<K, V> head : oldBuckets) {
102            Entry<K, V> current = head;
103            while (current != null) {
104                put(current.key, current.value);
105                current = current.next;
106            }
107        }
108    }
109}

How Collisions Work

Two different keys can produce bucket indexes that point to the same slot. That is a collision. In the example above, collisions are handled by chaining entries into a linked list inside that bucket.

That means put and get do not stop at the array lookup. They also walk the bucket chain to find the matching key.

If the hash function distributes keys well and the table resizes at a reasonable load factor, the average-case access remains fast.

Why Resizing Matters

Without resizing, the bucket chains keep getting longer as the map fills up. That pushes performance away from the expected near-constant-time behavior.

The example resizes when the load factor exceeds 0.75. During resize, the implementation allocates a larger bucket array and re-inserts every entry. Re-insertion is important because bucket indexes depend on the current array length.

What This Example Leaves Out

A production-grade hash map has more behavior than this learning implementation. For example:

  • iterators and views for keys and values
  • concurrent access safety
  • tree-based bucket optimization for heavy collisions
  • fail-fast iteration behavior
  • more careful performance tuning around hashing and resizing

That is normal. The goal here is understanding the structure, not re-implementing the entire JDK.

Common Pitfalls

The most common mistake is forgetting to compare keys with Objects.equals, which breaks support for null keys and normal object equality.

Another issue is resizing by copying nodes without recomputing their indexes. When capacity changes, the bucket mapping changes too.

A third problem is assuming collisions are rare enough to ignore. They are fundamental to hash-table design and must be handled correctly.

Summary

  • A hash map uses an array of buckets plus a hash-to-index calculation.
  • Collisions are commonly handled with chaining inside each bucket.
  • 'put, get, and remove all work by locating the bucket and then scanning the chain.'
  • Resizing is essential for keeping average lookup performance fast.
  • A custom implementation is valuable for learning even if the JDK version is much more advanced.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.