What is Load Factor in HashMap
The Load Factor is a fractional metric (float) that defines how full the hash table is permitted to become before its bucket array is automatically doubled in size (a resize() o...
🟢 Junior Level
Short Interview Answer (30 seconds)
The Load Factor is a fractional metric (float) that defines how full the hash table is permitted to become before its bucket array is automatically doubled in size (a resize() operation).
By default, HashMap sets the load factor to 0.75 (75%).
How It Works
Upon instantiation, HashMap allocates a bucket array with an initial capacity (capacity, default 16). The expansion threshold (threshold) is computed as:
import java.util.HashMap;
import java.util.Map;
public class LoadFactorDemo {
public static void main(String[] args) {
// Default HashMap: capacity = 16, loadFactor = 0.75 -> threshold = 12
Map<Integer, String> map = new HashMap<>();
for (int i = 1; i <= 12; i++) {
map.put(i, "Value " + i); // Up to 12 elements, no resize occurs
}
// Inserting the 13th element (size > threshold):
// Triggers resize(): capacity doubles to 32, threshold becomes 24
map.put(13, "Value 13");
}
}
Real-World Analogy: Imagine a bus with 16 seats. Company policy dictates that as soon as 75% of the seats are occupied (12 passengers), a second bus is dispatched (doubling capacity to 32) before overcrowding occurs at the entrance doors, ensuring passengers can board swiftly without queuing.
🟡 Middle Level
Balancing Memory Footprint and Search Latency
The default loadFactor = 0.75 represents an empirically optimized compromise between memory consumption and algorithmic search latency for get() and put():
| Load Factor Setting | Memory Consumption | Collision Probability | Lookup / Insertion Speed |
|---|---|---|---|
Low (0.50f) |
High (half the buckets remain empty) | Minimal (buckets hold 0 or 1 element) | Maximum ($O(1)$ directly on array slot) |
Standard (0.75f) |
Balanced | Low (collisions are rare) | $O(1)$ average case |
High (0.90f - 1.0f) |
Low (dense bucket packing) | High (frequent collision chaining) | Slower (iterating linked list chains) |
Overloaded (> 1.0f) |
Table size smaller than element count | Guaranteed Collisions | Degrades toward linear scan $O(N)$ |
Customizing loadFactor via Constructors
Java allows configuring the load factor explicitly:
Map<String, String> fastMap = new HashMap<>(64, 0.5f);
Map<String, String> compactMap = new HashMap<>(64, 0.9f);
- Validation Constraints: The
loadFactormust be strictly positive (> 0) and must not beFloat.NaN. Otherwise, the constructor throwsIllegalArgumentException:if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
Impact on Full-Table Iteration
The time required to iterate over a HashMap (keySet(), values(), entrySet()) is directly proportional to the sum of the bucket capacity and element count:
If an excessively low loadFactor is chosen (e.g., 0.1f), the table capacity balloons relative to a modest element count. Iterators are forced to inspect thousands of empty null slots in table[], severely degrading full traversal throughput.
🔴 Senior Level
Mathematical Proof: Poisson Distribution and Average $\lambda \approx 0.5$
The OpenJDK engineers documented the statistical justification for 0.75 in comments within HashMap.java.
Under ideal uniform hashing, the distribution of elements across buckets follows a Poisson distribution:
\[P(k) = \frac{e^{-\lambda} \lambda^k}{k!}\]where $k$ is the number of elements in a given bucket, and $\lambda$ is the expected average bucket occupancy.
Although the maximum threshold is 0.75, immediately after table doubling the actual load factor drops to 0.375. Over the life of the map, the time-averaged occupancy is approximately 0.5 ($\lambda \approx 0.5$).
Evaluating Poisson probabilities for $\lambda = 0.5$:
- 0 elements: $60.65\%$ ($e^{-0.5} \approx 0.6065$)
- 1 element: $30.33\%$
- 2 elements: $7.58\%$
- 3 elements: $1.26\%$
- 4 elements: $0.16\%$
- 5 elements: $0.016\%$
- 6 elements: $0.0013\%$
- 7 elements: $0.000094\%$
- 8 elements: $\approx 0.00000006$ ($6 \times 10^{-8}$, or 6 in 100 million!)
This mathematical proof explains why the treeification threshold is set to 8: reaching 8 items in a single bucket under normal distribution is statistically impossible. If a bucket reaches 8 nodes, it almost certainly signals a hash algorithm flaw or an active Hash Flooding DoS attack.
Internal Lifecycle of the threshold Field
In HashMap, the load factor is stored as final float loadFactor;:
- Lazy Allocation: Calling
new HashMap(initialCapacity, loadFactor)does not allocate the backing array. Thethresholdfield is temporarily reused to store the initial capacity calculated viatableSizeFor(initialCapacity):this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); - First
putValCall: The first write operation invokesresize(). It readsoldThr, allocates aNode<K,V>[]array of size $2^k$, and calculates the true operational threshold:newThr = (int)(newCap * loadFactor); this.threshold = newThr; MAXIMUM_CAPACITYBoundary: When capacity reaches $2^{30}$ (1 << 30),thresholdis set toInteger.MAX_VALUE($2^{31} - 1$), permanently disabling further resize operations.
Hardware Microarchitecture: CPU Cache Line Locality
Under high load factors (e.g., $0.95$ or $1.2$):
- Table memory overhead drops, but average collision chain length increases.
- Iterating linked
Nodeobjects involves pointer chasing across scattered heap addresses, triggering frequent L1/L2/L3 CPU Cache Misses. Fetching data from main DRAM requires 60–100 ns versus 1–4 ns from an L1 cache hit. - In low-latency architectures (JMH benchmarks), tuning
loadFactor = 0.5can improve read throughput by 30–40% by maximizing direct hits on the contiguoustable[]array without dereferencingNode.nextpointers.
🎯 Interview Cheat Sheet
1. 30-Second Elevator Pitch
The Load Factor (default 0.75f) is the fill ratio that triggers table doubling: $\text{threshold} = \text{capacity} \times \text{loadFactor}$. It balances memory utilization against lookup latency. Statistical analysis using the Poisson distribution proves that with $\lambda \approx 0.5$, the probability of an 8-node collision chain is negligible ($6 \times 10^{-8}$), establishing the rationale for the treeification threshold of 8. HashMap never shrinks its array when elements are removed.
2. 4 Tricky Interview Questions with Deep Answers
Question 1: Can you pass loadFactor = 2.0f or loadFactor = Float.POSITIVE_INFINITY? What happens?
Answer:
loadFactor = 2.0f: Completely valid. The constructor verifiesloadFactor > 0and accepts it. The table will only resize whensize > 2 * capacity(e.g., at 33 elements for capacity 16). Each bucket averages 2 entries before resizing.loadFactor = Float.POSITIVE_INFINITY: Valid syntactically (> 0and not NaN). When calculatingnewThr = (int)(newCap * loadFactor), floating-point overflow casts toInteger.MAX_VALUE. The table will never resize, remaining locked at its initial capacity and growing infinite collision chains.
Question 2: Why can’t loadFactor be changed on an existing HashMap instance?
Answer:
The field is declared final float loadFactor; and initialized exclusively in the constructor. Allowing dynamic modification would require instantaneous threshold recalculation and potentially trigger a massive, blocking resize() operation midway through an arbitrary business operation. To change the load factor, instantiate a new HashMap(capacity, newLoadFactor) and populate it via putAll().
Question 3: How do you size a HashMap to hold $N$ elements without triggering a single resize()?
Answer:
The table capacity must satisfy:
\(\text{capacity} \times \text{loadFactor} \ge N \implies \text{capacity} \ge \frac{N}{\text{loadFactor}}\)
For standard loadFactor = 0.75f, divide by $0.75$ (multiply by $\frac{4}{3}$):
int initialCapacity = (int) Math.ceil(expectedSize / 0.75f);
In Java 19+, use the canonical factory method:
Map<Key, Value> map = HashMap.newHashMap(expectedSize);
which internally executes (int) Math.ceil(expectedSize / 0.75F).
Question 4: Why doesn’t HashMap shrink when entries are deleted via remove()?
Answer:
- Preventing Thrashing (Flapping): If
HashMapdownsized when falling below a threshold, alternatingput()andremove()calls on boundary thresholds would cause continuous resizing and re-hashing cycles, devastating throughput. - Allocation Assumptions: Memory allocations assume that if an application reached $N$ entries once, it will likely require that capacity again in subsequent processing loops.
3. Common Pitfalls & Red Flags
-
❌ “Load factor 0.75 means 75% of buckets contain entries.”
Correction: It means total mapsizereaches 75% of total capacity; multiple entries can reside within the same bucket due to collisions. -
❌ “Setting load factor to 1.0 is ideal because it uses 100% of memory.”
Correction: It guarantees long collision chains and severe CPU cache misses, degrading latency. -
❌ “Creating a map via
new HashMap<>(N)prevents resizing when inserting N elements.”
Correction: Passing $N$ sets capacity to $N$; threshold becomes $0.75 \times N$. Inserting element $0.75N + 1$ triggers a resize. Always useHashMap.newHashMap(N). -
❌ “HashMap shrinks automatically when elements are cleared.”
Correction: The internal array retains its allocated size until the entire map instance is garbage collected.
4. Related Topics
- What is Capacity in HashMap — Capacity boundaries and defaults
- When Does Rehashing Occur in HashMap — Resize triggers
- What Happens When 8 Elements Are Reached in One Bucket — Poisson connection to treeification
- How to Choose the Initial Capacity for HashMap — Presizing best practices
- What Happens During Rehashing — Array doubling mechanics