skip to content

How do you correctly size a HashMap to hold a known number of entries without triggering a resize, and why isn't passing the entry count directly enough?

level: middleimportance: must knowfreq 60%

answer

  1. Constructor arg = bucket capacity, not entry count
  2. Resize threshold = capacity × load factor (0.75)
  3. capacity = n / 0.75 + 1
  4. Resize = double table + rehash every entry (O(n))
  5. JDK 19+: HashMap.newHashMap(n) / HashSet.newHashSet(n)

basics

~20 s

Pass capacity = expectedSize / 0.75 + 1, not the raw count. A HashMap resizes when it's about 75% full (the load factor), so if you pass the exact count it will still grow once you near it. Java 19+ has HashMap.newHashMap(n) that does this for you.

solid answer

~40 s

A HashMap's constructor argument is the initial bucket-array capacity, not the number of entries it can hold. The map automatically resizes (doubles its bucket array and rehashes every entry) once the number of entries exceeds capacity × load factor, where the default load factor is 0.75. So if you call new HashMap<>(100) and insert 100 entries, it resizes around entry 76 — defeating the point. To hold n entries without a resize you size for headroom: capacity = (int)(n / 0.75) + 1. HashMap also rounds capacity up to the next power of two internally. From Java 19, HashMap.newHashMap(n), Set, and Map factory helpers do this calculation, so prefer them. Pre-sizing avoids the doubling-and-rehash cost, which is more expensive than ArrayList's copy because every key's bucket must be recomputed.

code

java · 12 lines
java
// Sizing a HashMap for a known number of entries
int expected = 1000;

// Wrong: capacity is bucket count, not entry count → resizes ~entry 750
Map<String, Integer> wrong = new HashMap<>(expected);

// Right (manual rule): account for the 0.75 load factor
int capacity = (int) (expected / 0.75f) + 1;
Map<String, Integer> right = new HashMap<>(capacity);

// Best (JDK 19+): factory takes the expected element count directly
Map<String, Integer> best = HashMap.newHashMap(expected);

go deeper

for a junior

Knows a HashMap has an initial capacity argument and that giving it a size can avoid growth; may not know the load-factor adjustment.

for a middle

Correctly applies capacity = n/0.75 + 1, explains the load factor and the double-plus-rehash resize, and knows HashSet shares the behavior.

for a senior

Articulates why rehash is costlier than an array copy, mentions power-of-two rounding and HashMap.newHashMap(n), and reasons about when the optimization is worthwhile.

for a principal

Weighs load-factor tuning trade-offs (memory vs collision rate) at a system level, standardizes the factory helpers across a codebase, and ties sizing to measured allocation/GC behavior under load.

## What HashMap stores and how lookups work A **HashMap** maps keys to values. Internally it holds an array of **buckets** (slots), `Node[] table`. To find where a key goes, it computes the key's **hash code**, spreads the bits, and reduces it to an index `hash & (table.length - 1)` — which is why the table length is always a **power of two** (that masking trick only works then). Each bucket holds the entries whose keys landed on that index (a short linked list, or a balanced tree once a bucket gets large). Good distribution means most buckets hold 0–1 entries, giving **average O(1)** `get`/`put`. ## The load factor and why HashMap resizes If you keep adding entries into a fixed number of buckets, buckets get crowded, collisions rise, and lookups degrade toward O(n). To keep buckets sparse, HashMap tracks a **threshold = capacity × load factor**. The **load factor** is the maximum fullness ratio before growing; the default is **0.75** (75%). When `size` exceeds the threshold, HashMap **resizes**: 1. Allocates a new table **double** the length, 2. **Rehashes** — recomputes each existing entry's index for the new length and moves it. Rehashing is O(n) and touches every entry, making it costlier than an `ArrayList` copy (which just moves references). Each resize also discards the old table (GC pressure). ## Why passing the raw count fails Because the threshold is `capacity × 0.75`, a HashMap created with capacity `n` can only hold ~`0.75 × n` entries before it grows. So: ``` new HashMap<>(100); // threshold ≈ 75 (actually 96×0.75 after power-of-two rounding) // insert 100 entries → resizes at least once ``` The constructor argument is **bucket capacity**, not **entry capacity** — a classic confusion. ## The correct sizing rule To guarantee no resize while inserting `n` entries, you need `capacity × 0.75 > n`, i.e. `capacity > n / 0.75`. The conventional, safe formula is: ```java int capacity = (int) (expectedSize / 0.75f) + 1; Map<K, V> map = new HashMap<>(capacity); ``` The `+ 1` covers the strict-inequality / rounding edge. HashMap then rounds `capacity` **up to the next power of two** internally (e.g. you ask for 134, it uses 256). So the result is always at least enough. ### Java 19+ shortcut Manually doing this math is error-prone, so JDK 19 added factory helpers that take the **expected element count** and apply the formula for you: ```java Map<K, V> map = HashMap.newHashMap(expectedSize); Set<E> set = HashSet.newHashSet(expectedSize); ``` Prefer these when available — they encode the rule correctly and read clearly. (`HashSet` is backed by a `HashMap`, so the same load-factor logic and the same sizing concern apply to it.) ## Common misuse and edge cases - **Copy constructor:** `new HashMap<>(otherMap)` already sizes itself for the source's entries — you don't redo the math. - **Over-sizing:** a too-large capacity wastes memory (a big, mostly-empty bucket array) and slightly hurts cache locality, but never breaks correctness. - **Custom load factor:** you can pass a second constructor arg; higher (e.g. 0.9) saves memory but raises collisions, lower (e.g. 0.5) speeds lookups at memory cost. The default 0.75 is the tuned compromise — change it only with measurement. ## Bottom line Size a `HashMap`/`HashSet` for **headroom**, not the raw count: `n / 0.75 + 1`, or just `HashMap.newHashMap(n)` on modern JDKs. This avoids the double-and-rehash work entirely on known-large builds.

  • What is the cost difference between a HashMap resize and an ArrayList resize?
    Both allocate a bigger backing array and discard the old one. But ArrayList only block-copies element references (System.arraycopy). HashMap must rehash — recompute each entry's bucket index for the new, larger table and relink it — touching every entry individually. So a HashMap resize is generally more expensive per element than an ArrayList grow.
  • Does the same sizing rule apply to HashSet?
    Yes. HashSet is implemented on top of a HashMap (elements are keys), so it has the same 0.75 load factor and the same resize behavior. Use the same n/0.75+1 capacity, or HashSet.newHashSet(n) on JDK 19+.

saying these in an interview costs you the question

  • Passing the expected entry count directly as the capacity
  • Thinking the load factor is the same as initial capacity
  • Believing HashMap rehash just copies references like ArrayList (it recomputes every bucket index)
  • Assuming capacity is used as-is rather than rounded up to a power of two

context