HashMap

HashMap internals, hash collisions, load factor tuning, and why HashMap is the most important collection to understand deeply.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: HashMap stores key-value pairs in an array of buckets. It uses hashCode() to find the right bucket and equals() to find the right key within the bucket. Average O(1) for get/put/remove. The most common collection in enterprise Java — used for caching, grouping, lookup tables, and configuration.

🔍 What Is It?

HashMap is java.util.Map backed by a hash table.

Internal Structure (Java 8+):

buckets array: [bucket0][bucket1][bucket2]...[bucket15]
                   |
              LinkedList (or TreeNode if > 8 entries)
              [key1=val1] -> [key2=val2] -> null

Key Parameters:

  • →Initial capacity: 16 (number of buckets)
  • →Load factor: 0.75 (resize when 75% of buckets are used)
  • →Threshold: capacity * loadFactor = point at which resize happens
  • →Resize: doubles capacity, rehashes all entries — O(n)

Java 8 Improvement:

When a bucket has more than 8 entries (treeify threshold), it converts from a linked list to a balanced tree. This changes worst-case from O(n) to O(log n) for buckets with many collisions (e.g., when hashCode() returns the same value for all keys).

🎯 Why Do We Need It?

HashMap provides O(1) average lookup by key — impossible with a List (O(n) search). It is the foundation of caching, indexing, and any operation where you need to find data by a key rather than iterating through everything.

🏗️ What Happens in Real Projects?

HashMap is everywhere:

// Configuration
Map<String, String> config = new HashMap<>();
config.put("db.host", "localhost");
String host = config.getOrDefault("db.host", "127.0.0.1");

// In-memory cache
Map<Long, User> userCache = new HashMap<>();
User user = userCache.computeIfAbsent(userId, id -> db.findUser(id));

// Grouping/indexing
Map<String, List<Product>> byCategory = products.stream()
    .collect(Collectors.groupingBy(Product::getCategory));

⚙️ How It Works

put(key, value) algorithm:

  1. If key is null: store in bucket 0
  2. Compute hash: (h = key.hashCode()) ^ (h >>> 16) (spread high bits)
  3. Find bucket: hash & (capacity - 1) (fast modulo for power-of-2 capacity)
  4. Traverse bucket's linked list/tree looking for existing key (using equals())
  5. If found: replace value; if not found: add new entry
  6. If size > threshold: resize (double capacity, rehash all)

Why capacity is always a power of 2:

Finding the bucket with hash & (capacity-1) is a single bitwise AND operation — much faster than hash % capacity.

hashCode() and equals() contract:

If a.equals(b) then a.hashCode() == b.hashCode()

(but not vice versa — different objects can have the same hash = collision)

💻 Example

HashMap Deep Dive — Understanding HashMap internals through code.

java
// Demonstrate why equals/hashCode matters
class BadKey {
    int value;
    BadKey(int v) { this.value = v; }
    // NO equals/hashCode override!
}

Map<BadKey, String> bad = new HashMap<>();
BadKey k1 = new BadKey(42);
bad.put(k1, "hello");
System.out.println(bad.get(k1));          // "hello" (same reference)
System.out.println(bad.get(new BadKey(42))); // null! (different object)

// Fix: proper equals/hashCode
record GoodKey(int value) {}  // Record auto-generates equals/hashCode

Map<GoodKey, String> good = new HashMap<>();
good.put(new GoodKey(42), "hello");
System.out.println(good.get(new GoodKey(42)));  // "hello" ✓

// Pre-sizing for known capacity
// To store 100 entries without resize:
// capacity = 100 / 0.75 = 134, next power of 2 = 256
Map<String, Object> preAllocated = new HashMap<>(256);

// Observe resize behavior
Map<Integer, Integer> map = new HashMap<>(4);  // capacity 4
// Resize at 4 * 0.75 = 3 entries
map.put(1, 1);  // size=1, no resize
map.put(2, 2);  // size=2, no resize
map.put(3, 3);  // size=3 = threshold, RESIZE to capacity 8!
map.put(4, 4);  // size=4, no resize (threshold now 6)

🤔 Common Confusions

❓ Can HashMap have null keys and null values?

Yes. HashMap allows one null key (stored in bucket 0) and multiple null values. ConcurrentHashMap does NOT allow null keys or values — it throws NullPointerException. Hashtable also does not allow nulls. In practice, avoid null keys — use Optional or a sentinel value instead.

❓ What is the load factor and should I change it?

Load factor (default 0.75) controls when the map resizes. Lower load factor (e.g., 0.5) means more resizing but fewer collisions — faster lookups, more memory. Higher load factor (e.g., 0.9) means fewer resizes but more collisions — slower lookups, less memory. The default 0.75 is a well-tested balance. Only change it if you have profiled a specific need.

🚨 Common Production Issues

HashMap in multithreaded code — can cause infinite loops (Java 7, due to resize) or data corruption. Always use ConcurrentHashMap in concurrent contexts.

Poorly distributed hashCode — if all keys hash to the same value, HashMap degrades to O(n) (or O(log n) in Java 8+). This is a DoS vector for web applications — attackers can craft inputs that collide. Java randomizes String hashing since Java 7.

Frequent resizing — if you add 10,000 elements to a default HashMap, it resizes ~10 times. Pre-size: new HashMap<>(10000 * 4 / 3 + 1) to avoid resizing.

✅ Best Practices

Pre-size HashMap when you know the approximate number of entries.

Always implement equals() and hashCode() for custom key types.

Use ConcurrentHashMap for thread-safe maps.

Prefer computeIfAbsent() over get() + null check + put().

Use merge() for counting and accumulation — it is cleaner than compute().

For enum keys, use EnumMap — it is faster and uses less memory.

🏛️ Architect's Note

Senior Architect's Perspective

HashMap is the data structure behind most in-memory caches. However, for production caching, use Caffeine (local) or Redis (distributed). They add: expiration (TTL), maximum size with eviction, statistics, and distributed support. The Map interface abstraction means you can switch from HashMap to Caffeine with minimal code changes.

💬 Frequently Asked Questions

What is the difference between HashMap and ConcurrentHashMap?

HashMap is not thread-safe — concurrent access can cause data corruption or infinite loops. ConcurrentHashMap uses CAS (Compare-And-Swap) operations and fine-grained locking for thread safety without a global lock. It supports full concurrency for reads and configurable concurrency for writes. It does NOT allow null keys/values. Use ConcurrentHashMap whenever a map is shared between threads.

What happens to HashMap during resizing?

When the number of entries exceeds capacity * loadFactor, HashMap creates a new array twice as large and rehashes every existing entry into the new array. This is an O(n) operation. During this time, the map is temporarily inconsistent — this is why HashMap is not thread-safe. ConcurrentHashMap handles resizing incrementally to avoid this.

🎯 Key Takeaways

1HashMap: O(1) average for get/put/remove; no ordering guarantee
2Requires correct equals() and hashCode() on keys
3Java 8+: buckets with 8+ entries convert to balanced trees (O(log n) worst case)
4Default capacity 16, load factor 0.75 — pre-size for known large maps
5Not thread-safe — use ConcurrentHashMap for concurrent access
6computeIfAbsent() and merge() are the most useful modern HashMap methods

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Core Java learning path

Browse the full Core Java topic collection and learning order on SplashRide.

Core Java Interview Prep

Practice Core Java interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Java Map Interface Explained: HashMap, LinkedHashMap, and TreeMap

Learn the Java Map interface with HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap, key-value lookup, ordering, performance, and real-world examples.

HashSet

HashSet internals, when to use it over a List, and the equals/hashCode contract that makes or breaks it.