Java HashMap vs ConcurrentHashMap: Complete Interview Guide

In Java collections framework, HashMap and ConcurrentHashMap are two of the most frequently discussed topics in technical interviews. While HashMap provides fast key-value storage for single-threaded environments, ConcurrentHashMap extends these capabilities to support concurrent access. This comprehensive guide covers essential interview questions about both data structures, their internal workings, and key differences.


HashMap Fundamentals

What is HashMap?

HashMap is a hash table based implementation of the Map interface in Java. It stores key-value pairs and provides constant-time performance for basic operations like get() and put() assuming the hash function disperses elements properly. HashMap is part of the Java Collections Framework and resides in java.util package.

How HashMap Works Internally

The internal architecture of HashMap consists of an array of buckets where each bucket is a linked list (or tree in Java 8+) of entries. When you store a key-value pair, HashMap calculates the hash of the key to determine the bucket location. In Java 8+, when a bucket contains too many entries (default threshold is 8), it converts the linked list into a balanced red-black tree for faster lookups.

Key Concepts of HashMap

  • Initial Capacity: The default initial capacity is 16
  • Load Factor: The default load factor is 0.75
  • Threshold: The point at which resizing occurs (capacity * load factor)
  • Bucket: Each index in the hash table array
  • Collision: When two keys produce the same hash value
  • Treeify Threshold: When linked list converts to tree (default 8)

HashMap Interview Questions

Q1: How does the put() method work internally in HashMap?

The put() method follows these steps:

  1. Calculates hash code of the key using hashCode() method
  2. Applies internal hashing function to distribute hash codes evenly across buckets
  3. Determines index position using: index = (n - 1) & hash where n is table length
  4. If bucket is empty, creates new node and places it
  5. If bucket has entries, traverses the linked list/tree to check for duplicate keys
  6. If duplicate key found, replaces the value
  7. If no duplicate, adds new node at the end
  8. Checks if resizing is needed based on threshold
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

Q2: What is the equals() and hashCode() contract in HashMap?

The contract states that:

  • If two objects are equal according to equals() method, they must have the same hashCode()
  • If two objects have the same hashCode(), they are not required to be equal
  • Violating this contract causes HashMap to behave incorrectly

Important: When using custom objects as keys, always override both equals() and hashCode() methods.

Q3: How does HashMap handle collisions?

When two different keys produce the same hash value (collision), HashMap stores both entries in the same bucket as a linked list. In Java 8+, if the linked list length exceeds 8, it converts to a red-black tree structure. During retrieval, HashMap traverses this list/tree and uses equals() method to find the exact match.

Q4: What is the significance of load factor in HashMap?

The load factor determines when HashMap should resize. With a load factor of 0.75, when the map is 75% full, it triggers resizing. This value balances time and space costs:

  • Lower load factor: More memory usage but fewer collisions
  • Higher load factor: Less memory usage but more collisions

The default 0.75 provides good trade-off between performance and space.

Q5: Explain the resizing process in HashMap

When the number of entries exceeds the threshold (capacity * load factor), HashMap:

  1. Creates a new array with double the capacity
  2. Rehashes all existing entries to distribute them in the new array
  3. Updates the threshold value

In Java 8+, entries are either kept at same index or moved to index + oldCapacity position, optimizing the rehashing process.

Q6: Why does HashMap allow null keys and values?

HashMap permits one null key and multiple null values. The null key is handled specially:

  • Hash code for null key is always 0
  • Null key is stored at index 0 in the bucket array
  • This design choice provides flexibility for common use cases

Q7: Can we use mutable objects as keys in HashMap?

Using mutable objects as keys is dangerous. If the object’s state changes after being used as a key, its hashCode() may change, making it impossible to retrieve the value. Always use immutable objects (like StringInteger) as keys for predictable behavior.

Q8: What is the difference between HashMap and HashTable?

FeatureHashMapHashTable
Thread SafetyNot thread-safeThread-safe (synchronized methods)
Null Keys/ValuesAllows one null key and null valuesDoes not allow null keys or values
PerformanceFaster (no synchronization overhead)Slower due to synchronization
InheritanceExtends AbstractMapExtends Dictionary
IteratorFail-fast iteratorEnumerator (not fail-fast)

Q9: What is fail-fast iterator in HashMap?

The iterators returned by HashMap are fail-fast. If the map is structurally modified after iterator creation (except through iterator’s own remove method), a ConcurrentModificationException is thrown. This behavior prevents non-deterministic behavior in concurrent scenarios.

Q10: Is HashMap thread-safe? How to make it thread-safe?

NoHashMap is not thread-safe. Multiple threads modifying it concurrently can cause data corruption and infinite loops. To make it thread-safe:

  • Use Collections.synchronizedMap(new HashMap<>())
  • Use ConcurrentHashMap for better concurrent performance
  • Use external synchronization (not recommended)

ConcurrentHashMap Fundamentals

What is ConcurrentHashMap?

ConcurrentHashMap is a thread-safe variant of HashMap that allows concurrent read and write operations without compromising consistency. It was introduced in Java 5 as part of the java.util.concurrent package and provides better scalability than Collections.synchronizedMap() or Hashtable.

How ConcurrentHashMap Achieves Thread Safety

Unlike Hashtable which synchronizes every method, ConcurrentHashMap uses sophisticated techniques:

  • Java 7 and earlier: Used Segment-based locking with 16 default segments
  • Java 8+: Uses lock-free reads and synchronized bucket-level locking with CAS operations
  • Only the bucket being modified is locked, allowing parallel operations on other buckets

Key Concepts of ConcurrentHashMap

  • Segment: Divides map into independent partitions (Java 7)
  • Concurrency Level: Defines number of segments (default 16)
  • Volatile Variables: Ensures visibility of changes across threads
  • CAS Operations: Compare-and-swap for lock-free operations

ConcurrentHashMap Interview Questions

Q1: How does ConcurrentHashMap achieve better concurrency than Hashtable?

ConcurrentHashMap uses lock striping instead of locking the entire map. In Java 8+, it synchronizes only at the bucket level using synchronized keywords on the first node of the bucket. This allows multiple threads to operate on different buckets simultaneously, dramatically improving throughput in high-concurrency scenarios.

Q2: What is concurrency level in ConcurrentHashMap?

The concurrency level determines the number of segments (in Java 7) or the initial size of the table for lock striping (in Java 8+). Default is 16, meaning up to 16 threads can write simultaneously without blocking. This value is set at construction and cannot be changed later.

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
    // Initial implementation details
    this.concurrencyLevel = concurrencyLevel;
}

Q3: How does the put() method work in ConcurrentHashMap?

The put() operation in Java 8+:

  1. Calculates hash and determines bucket index
  2. Uses CAS to ensure atomic insertion if bucket is empty
  3. If bucket is not empty, synchronizes on the first node of the bucket
  4. Traverses the list/tree to find or add the entry
  5. May resize if threshold is exceeded
  6. Updates size and counter values atomically

Q4: Can multiple threads read from ConcurrentHashMap simultaneously?

YesConcurrentHashMap allows lock-free reads. Read operations do not require any locking and can proceed concurrently with write operations. This is achieved through volatile reads and careful memory visibility guarantees.

Q5: What is the size() method behavior in ConcurrentHashMap?

The size() method returns an approximate count because the map might be undergoing concurrent modifications. It iterates through all segments and sums up the counts, which may not reflect exact size at any given moment. In Java 8+, it uses a counter that may be slightly stale but is faster.

Q6: How does ConcurrentHashMap handle null keys and values?

ConcurrentHashMap does not allow null keys or values. This design decision prevents ambiguity in concurrent contexts. If a get() returns null, it’s unclear whether the key doesn’t exist or the value is null. With non-null restriction, null always means key not found.

Q7: What is the performance impact of lock striping?

Lock striping divides the map into independently lockable sections, reducing contention. Benefits include:

  • Multiple writes can proceed on different segments/buckets
  • Read operations remain completely lock-free
  • Scales well with number of threads up to concurrency level
  • Memory overhead is minimal compared to per-entry locking

Q8: How does compute() method work atomically?

The compute() method provides atomic updates. It locks the bucket, computes the new value based on the existing value, and updates the entry atomically. This is crucial for operations like counters where you need get-update-put without race conditions.

concurrentMap.compute(key, (k, v) -> (v == null) ? 1 : v + 1);

Q9: What are the iteration characteristics of ConcurrentHashMap?

Iterators from ConcurrentHashMap are weakly consistent. They reflect the state of the map at iterator creation but may also reflect subsequent modifications. They do not throw ConcurrentModificationException and allow safe iteration during concurrent updates.

Q10: When should you use ConcurrentHashMap over Collections.synchronizedMap()?

Prefer ConcurrentHashMap when:

  • High concurrency is expected (multiple threads reading and writing)
  • Read operations significantly outnumber writes
  • You need better throughput and scalability
  • You want to avoid locking the entire map for single operations

Use Collections.synchronizedMap() only for legacy code or when you need strict serialization of access to the map.


Detailed Summary and Comparison

Understanding the distinctions between HashMap and ConcurrentHashMap is crucial for designing scalable Java applications. While HashMap excels in single-threaded scenarios with its lightweight structure, ConcurrentHashMap provides robust concurrency support without sacrificing too much performance.

Key Takeaways

  • Thread Safety: HashMap is not thread-safe; ConcurrentHashMap is thread-safe with fine-grained locking
  • Performance: HashMap is faster in single-threaded environments; ConcurrentHashMap scales better in multi-threaded environments
  • Null Handling: HashMap allows null keys and values; ConcurrentHashMap prohibits them
  • Locking Strategy: HashMap has no locking; ConcurrentHashMap uses bucket-level locking (Java 8+)
  • Iteration: HashMap uses fail-fast iterators; ConcurrentHashMap uses weakly consistent iterators
  • Memory Overhead: ConcurrentHashMap has slightly higher memory footprint due to concurrency control structures

Comprehensive Feature Comparison

FeatureHashMapConcurrentHashMap
Packagejava.utiljava.util.concurrent
Thread SafetyNoYes (lock striping)
Null KeyAllowed (one)Not Allowed
Null ValueAllowed (multiple)Not Allowed
Locking MechanismNo locksBucket-level synchronized locks
PerformanceO(1) best caseSlightly slower due to locking overhead
ScalabilityPoor in multi-threadingExcellent in multi-threading
Iterator TypeFail-fastWeakly consistent
ConcurrentModificationExceptionYes, can be thrownNo, never thrown
Default Capacity1616
Default Load Factor0.750.75
Concurrency LevelN/A16 (configurable)
Read OperationsNo synchronizationLock-free, volatile reads
Write OperationsNo synchronizationBucket-level synchronization
Memory FootprintLowerHigher (counter cells, etc.)
Use CaseSingle-threaded or externally synchronizedHigh-concurrency applications

When to Use Which?

Use HashMap when:

  • You are working in a single-threaded environment
  • You need maximum performance without concurrency overhead
  • You need to store null keys or values
  • You can manage external synchronization manually if needed

Use ConcurrentHashMap when:

  • Building multi-threaded applications with shared state
  • Read operations heavily dominate write operations
  • You need atomic operations like compute()merge()
  • You want to avoid locking the entire data structure
  • You need weakly consistent iteration during updates

Final Advice: For interview preparation, understand not just the API differences but the internal implementation details—how hashing works, collision resolution, resizing mechanics, and the evolution from segment locking to bucket locking. Be prepared to discuss performance implications, memory models, and real-world scenarios where each implementation shines.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.