Open any Java codebase and count the collection types. ArrayList wins, and HashMap is right behind it. Caching, counting, grouping, indexing, de-duplicating — HashMap is the workhorse for all of them because it turns "find X" from a scan into a single hash lookup.
This guide covers the operations you will use every day, every iteration pattern with its trade-offs, and the best practices that separate working code from production-quality code. For the internals — buckets, treeification, resizing — see the companion deep dive on how HashMap works internally.
What HashMap gives you
HashMap<K, V> implements the Map interface with a hash table. Its contract in five points:
- Average O(1) for
put,get,removeandcontainsKey - No ordering guarantee — iteration order can change as the map grows
- One null key and unlimited null values are allowed
- Not thread-safe — see HashMap vs ConcurrentHashMap for the concurrent story
- Fail-fast iterators — structural modification during iteration throws
ConcurrentModificationException
| Operation | Average | Worst case (Java 8+) |
|---|---|---|
put(k, v) |
O(1) | O(log n) |
get(k) |
O(1) | O(log n) |
remove(k) |
O(1) | O(log n) |
containsKey(k) |
O(1) | O(log n) |
containsValue(v) |
O(n) | O(n) |
| iteration | O(capacity + size) | O(capacity + size) |
The worst case is O(log n), not O(n), because Java 8 converts long collision chains into balanced trees. That single fact answers a very common interview follow-up.
Core operations with a runnable example
import java.util.HashMap;
import java.util.Map;
public class HashMapOperations {
public static void main(String[] args) {
Map<String, Double> prices = new HashMap<>();
// Create / update
prices.put("idli", 40.0);
prices.put("dosa", 60.0);
Double old = prices.put("dosa", 65.0); // returns 60.0
prices.putIfAbsent("dosa", 99.0); // no effect, key exists
// Read
System.out.println(prices.get("dosa")); // 65.0
System.out.println(prices.get("vada")); // null
System.out.println(prices.getOrDefault("vada", 0.0)); // 0.0
// Delete
prices.remove("idli");
prices.remove("dosa", 100.0); // conditional: value mismatch, no-op
// Inspect
System.out.println(prices.containsKey("dosa")); // true
System.out.println(prices.size()); // 1
System.out.println(old); // 60.0
}
}
Three details worth noticing: put() returns the previous value (or null), putIfAbsent() only writes when the key is missing, and the two-argument remove(key, value) deletes only when the current value matches — handy for optimistic updates.
Common mistake: A common mistake beginners make is treating
nullfromget()as "key absent". It can also mean "key present, value is null". When that distinction matters, checkcontainsKey()— or better, never store null values in the first place.
The Java 8 methods you should be using
getOrDefault, computeIfAbsent, compute and merge replace almost every check-then-put pattern:
import java.util.*;
public class ModernHashMap {
public static void main(String[] args) {
String[] orders = {"veg", "chicken", "veg", "paneer", "veg"};
// Counting: one line instead of if-contains-else
Map<String, Integer> count = new HashMap<>();
for (String o : orders) {
count.merge(o, 1, Integer::sum);
}
System.out.println(count); // {chicken=1, veg=3, paneer=1}
// Grouping: computeIfAbsent creates the list only once
Map<Character, List<String>> byInitial = new HashMap<>();
for (String o : orders) {
byInitial.computeIfAbsent(o.charAt(0), k -> new ArrayList<>())
.add(o);
}
System.out.println(byInitial);
// {p=[paneer], c=[chicken], v=[veg, veg, veg]}
// Transform a value in place
count.compute("veg", (k, v) -> v == null ? 1 : v * 10);
System.out.println(count.get("veg")); // 30
}
}
The counting and grouping idioms above appear in a huge share of coding-round problems — two-sum, anagram grouping, frequency sorting. If you can write merge(word, 1, Integer::sum) without thinking, you have a real speed advantage in a timed collections interview round.
Iterating a HashMap: four patterns
1. entrySet() — the default
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
One pass, key and value together. Use this unless you have a reason not to.
2. forEach with a lambda
map.forEach((key, value) -> System.out.println(key + " = " + value));
Same performance, less ceremony. Best for short loop bodies.
3. keySet() or values() — when you need only one side
for (String key : map.keySet()) { /* keys only */ }
for (Integer val : map.values()) { /* values only */ }
Fine on their own. The anti-pattern is looping keySet() and calling map.get(key) inside — that hashes every key a second time for nothing.
4. Iterator — when you must remove while iterating
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
if (it.next().getValue() == 0) {
it.remove(); // safe structural removal
}
}
// Java 8 alternative:
map.entrySet().removeIf(e -> e.getValue() == 0);
Common mistake: A common mistake beginners make is calling
map.remove(key)inside a for-each loop. The iterator's modification count no longer matches the map's, and the very next iteration throwsConcurrentModificationException. UseIterator.remove()orremoveIf()instead.
Capacity, load factor and performance
A HashMap starts with 16 buckets and resizes — doubling the table and redistributing every entry — once size exceeds capacity × load factor (0.75 by default, so 12 entries). Resizing is O(n), and repeated resizes while loading a large dataset add up.
If you know the target size, pre-size the map:
// Will hold ~10,000 entries: avoid ~10 resize passes
Map<String, User> users = new HashMap<>(16384);
// Or let Java 19+ compute capacity from expected size:
// Map<String, User> users = HashMap.newHashMap(10000);
Choosing 16384 (the next power of two above 10000 / 0.75) means the map never resizes during the load. The mechanics of why capacity is always a power of two are covered in the internal working deep dive.
Pro tip: Don't tune load factor. Lowering it wastes memory for a marginal speedup; raising it increases collisions. In ten years of production Java, the default 0.75 is almost always the right answer — pre-sizing capacity is the optimization that actually matters.
Best practices checklist
Use immutable keys. String, Integer, enums and records are safe. If a key object's hashCode() changes after insertion, the entry is stranded in the wrong bucket and effectively lost.
Override hashCode() and equals() together. If your custom class is a key, both methods must agree: equal objects must produce equal hash codes. A record gives you both for free:
record EmployeeId(String dept, int number) {} // hashCode + equals generated
Map<EmployeeId, String> names = new HashMap<>();
names.put(new EmployeeId("ENG", 42), "Priya");
System.out.println(names.get(new EmployeeId("ENG", 42))); // Priya
Program to the interface. Declare Map<String, Integer>, not HashMap<String, Integer>, so you can swap implementations without touching call sites.
Pick the right sibling when order matters. LinkedHashMap for insertion order, TreeMap for sorted keys. A HashSet is literally a HashMap with dummy values, so the same reasoning applies to HashSet.
Never share a plain HashMap across writing threads. Lost updates and corrupted state are the symptoms; ConcurrentHashMap is the cure. The legacy Hashtable also "works" but at a heavy cost — the comparison with Hashtable explains why it lost.
Interview note: Interviewers often chain questions: "What's the complexity of get()?" then "When is it not O(1)?" then "What did Java 8 change about that?" The chain ends at treeification and the hashCode/equals contract. Prepare the whole chain, not just the first link.
Where to go next
You now have the practical layer: operations, iteration, capacity tuning and the key contracts. Two follow-ups complete the picture. First, the internal mechanics of HashMap — the most reliably asked deep-dive topic in Java interviews at every experience level. Second, the thread-safety story in HashMap vs ConcurrentHashMap, which matters the moment your code runs on more than one thread.
Frequently Asked Questions
What is the time complexity of HashMap operations?
How do I iterate over a HashMap in Java?
Can I modify a HashMap while iterating over it?
Is HashMap thread-safe in Java?
Why is HashMap iteration order not guaranteed?
What is the initial capacity of a HashMap?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

