Walk through what actually happens when a HashMap resizes, and why repeated resizing is expensive.
answer
- Resize = allocate double array + rehash every entry (O(n))
- Bucket index = hash & (capacity - 1); changes when capacity grows
- Repeated resizes = repeated allocations + GC + latency spikes
- Java 8: entry stays or moves to index + oldCapacity (one bit)
- Presize to avoid all of it: N / 0.75 + 1
basics
~20 sWhen a HashMap gets too full it allocates a new bucket array (double the size) and moves every existing entry into it, recomputing each one's bucket. That's O(n) work plus a big allocation, so doing it repeatedly while a map fills up wastes time and creates garbage.
solid answer
~50 sA HashMap resizes when its size exceeds capacity * loadFactor. Resizing allocates a new bucket array of double the capacity and redistributes ("rehashes") every entry into the new table, because the bucket index depends on the table size. That's O(n) work and a fresh allocation each time. As a map grows from empty, it crosses several thresholds (12, 24, 48, ...), so it can resize many times, each copying all current entries — total work that is amortized O(n) but with repeated allocations and GC pressure, and a latency spike on the call that triggers each resize. Java 8 made the rehash cheaper: because capacity is a power of two and doubles, each old bucket splits into at most two new buckets (the original index and index + oldCapacity), so entries move in a predictable, branch-light way without recomputing full hashes. The fix for the cost is presizing: give the constructor expectedSize / 0.75 + 1 so the map allocates once and never crosses a threshold.
go deeper
Knows resizing copies entries into a bigger array and that it costs time; may not detail rehashing.
Explains the double-and-rehash mechanism and the threshold crossings, and knows presizing avoids it.
Articulates O(n) cost, GC/latency implications, the Java 8 power-of-two split (index or index+oldCap), and the index = hash & (capacity-1) formula.
Reasons about tail latency, allocation/GC behaviour at scale, contrasts with ArrayList, and sets presizing as a standard to keep hot paths allocation-stable.
## Recap: capacity, threshold, buckets A `HashMap` holds entries in a **bucket array** of length `capacity` (always a power of two: 16, 32, 64, ...). A key's bucket index is derived from its hash code, typically `index = hash & (capacity - 1)` (a fast bitmask that works precisely because capacity is a power of two). The map grows when `size > capacity * loadFactor` — the **threshold** (default load factor 0.75, so threshold = capacity * 0.75). ## What a resize does, step by step 1. **Allocate** a new bucket array of **double** the capacity (16 → 32 → 64 → ...). 2. **Recompute placement** for every existing entry. Because the index formula `hash & (capacity - 1)` depends on `capacity`, an entry's bucket in the *new*, larger table may differ from its old bucket. This redistribution is called **rehashing**. 3. **Move** every entry into the new table. 4. **Update** capacity, threshold, and the table reference; the old array becomes garbage. This is **O(n)** — proportional to the number of entries — and involves a sizeable allocation plus touching every entry. ## Why *repeated* resizing hurts Starting empty with default capacity 16, the thresholds you cross as you add entries are 12, 24, 48, 96, ... Each crossing triggers a full rehash of everything currently stored. Insert a million entries from an unsized map and you resize ~17 times, each time copying all current entries. The *amortized* cost per insert is still O(1) (total copying is O(n) overall), **but**: - **Repeated large allocations** — each resize allocates a new array (the largest ones are huge), then discards the old one, creating **garbage** that pressures the GC and can cause pauses. - **Latency spikes** — the single `put` that triggers a resize does O(current size) work, so tail latency is bad even if average is fine. In a low-latency system that spike matters. - **Cache effects** — touching and rewriting the whole table churns CPU caches. ## Java 8 made the rehash cheaper (but not free) Pre-Java 8, resizing recomputed each entry's position and could reverse linked-list order (a thread-safety footgun under concurrent misuse). Java 8 exploits the power-of-two doubling: when capacity doubles, the bucket-index bitmask gains exactly one more bit. So each entry either **stays at its current index** or **moves to `index + oldCapacity`**, decided by a single bit of its hash (`hash & oldCapacity`). Each old bucket therefore splits into at most **two** new buckets ("lo" and "hi"), entries are relinked without recomputing full hashes, and order is preserved. This is faster and more predictable — but it's still O(n) and still allocates. ## The cure: presize If you know you'll store N entries, size the map so it never crosses a threshold: ```java // holds N entries without any resize Map<K,V> m = new HashMap<>((int)(N / 0.75f) + 1); // Java 19+: Map<K,V> m2 = HashMap.newHashMap(N); ``` Now there is **one** allocation and **zero** rehashes for the first N inserts: predictable latency, less garbage. ## Contrast with ArrayList `ArrayList` resizing is similar in spirit — allocate a bigger array (≈1.5×) and `System.arraycopy` the elements — but there's **no rehashing**: elements keep their positions, so the copy is a single bulk memory move (cheap per element). HashMap resizing is costlier per element because it must redistribute entries across buckets. ## Summary Resize = allocate-double + rehash-all = O(n) + a big allocation. Repeated resizes during growth mean repeated allocations, GC pressure, and latency spikes. Java 8's power-of-two split makes each rehash cheaper; presizing avoids them altogether.
- Amortized insertion into a HashMap is O(1), so why does resizing still matter in a latency-sensitive service?Amortized O(1) hides tail latency: the single put that crosses a threshold does O(current size) work plus a large allocation. In a low-latency or real-time path that spike (and the GC churn from discarded arrays) is exactly what you must avoid, which is why you presize.
- How does Java 8 avoid recomputing full hashes during a resize?Because capacity is a power of two and doubles, the index mask gains one bit. An entry either keeps its index or moves to index + oldCapacity, decided by hash & oldCapacity — a single bit test. So each bucket splits into at most two, with no full-hash recomputation and preserved order.
saying these in an interview costs you the question
- Saying resize is O(1) — the triggering put is O(current size)
- Thinking resizing recomputes nothing — it redistributes entries because the index depends on capacity
- Confusing ArrayList resize (bulk arraycopy, no rehash) with HashMap resize (full rehash)
- Claiming Java 8 made resize free — it made the per-entry rehash cheaper, still O(n) with allocation
- Assuming amortized O(1) means no latency problem — tail latency spikes on resize still matter