HashMap
HashMap internals, hash collisions, load factor tuning, and why HashMap is the most important collection to understand deeply.
Last reviewed: January 2025
🔍 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] -> nullKey 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:
- If key is null: store in bucket 0
- Compute hash:
(h = key.hashCode()) ^ (h >>> 16)(spread high bits) - Find bucket:
hash & (capacity - 1)(fast modulo for power-of-2 capacity) - Traverse bucket's linked list/tree looking for existing key (using equals())
- If found: replace value; if not found: add new entry
- 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.
🤔 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
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
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
Core Java Interview Prep
Practice interview questions that connect directly to this technology track.
↗ Where this topic fits
Core Java Technology Guide
Return to the Core Java learning hub.
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.
Sources
Sources / References
Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
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.