skip to content

When presizing collections from an existing collection or stream, what subtle capacity pitfalls should you watch for?

level: seniorimportance: nice to knowfreq 30%

answer

  1. new HashMap<>(n) resizes at 0.75n — pass n/0.75+1 instead
  2. Prefer HashMap.newHashMap(n) (Java 19+) / Guava withExpectedSize
  3. Collectors.toMap/toList can't be presized — use supplier overloads
  4. Capacity rounds up to a power of two
  5. Capacity is a hint, never a cap on size

basics

~20 s

The common trap is new HashMap<>(existingMap) or passing a size without accounting for the 0.75 load factor — the new map can still resize. Also, copy constructors and stream collectors often size to the source's size, not size/0.75, so they may resize anyway.

solid answer

~50 s

Several collection presizing idioms quietly fail to prevent resizing. new HashMap<>(otherMap) sizes the table from the source's size but still applies the 0.75 load factor, so a map copied from a full source can resize during the copy. Passing new HashMap<>(n) where n is the element count (not n / 0.75) is the classic mistake — it resizes at n * 0.75. Stream collectors like Collectors.toMap don't let you presize at all and grow dynamically. The fixes: use HashMap.newHashMap(n) (Java 19+) or Guava's Maps.newHashMapWithExpectedSize(n), which do the load-factor maths; for ArrayList, new ArrayList<>(collection) is fine because there's no load factor. Also remember HashMap rounds capacity up to a power of two, so over-allocation in chunks is expected, and that an initial capacity below the data size just means you resize anyway — it's a hint, never a cap.

go deeper

for a junior

May not be aware of these traps; at minimum should know you can pass a size to the constructor.

for a middle

Knows to divide the count by the load factor for HashMap and that ArrayList doesn't need it.

for a senior

Recognizes the copy-constructor and stream-collector pitfalls, reaches for newHashMap/Guava helpers, and understands power-of-two rounding and the hint-not-cap semantics.

for a principal

Codifies safe presizing helpers/conventions, evaluates whether the optimization is worth it via profiling, and is mindful of allocation chunking and GC at scale.

## Why presizing from a source is subtle The goal of presizing is: allocate the backing storage once, big enough that no resize happens while you fill it. But the convenient idioms don't always achieve that, because of the **load factor** and how the JDK chooses capacity. ## Pitfall 1: `new HashMap<>(int)` with the raw element count ```java Map<K,V> m = new HashMap<>(n); // BUG if you expect n entries to fit ``` This sets *capacity* to (the next power of two ≥) `n`, but the map resizes at `capacity * 0.75`. So a map you intended to hold `n` entries resizes once it passes `~0.75 * n`. To hold `n` without resizing you need `capacity >= n / 0.75`: ```java Map<K,V> m = new HashMap<>((int)(n / 0.75f) + 1); ``` ## Pitfall 2: the `HashMap(Map)` copy constructor ```java Map<K,V> copy = new HashMap<>(source); ``` Its implementation sizes the new table based on `source.size()` and the **default load factor**. Historically (and depending on JDK version) this has been a source of an extra resize: in some versions it used `source.size()` directly against the threshold, so copying a nearly-full map could trigger a resize during construction. The safe, version-independent move is to size explicitly with `newHashMap` if you care. ## Pitfall 3: stream collectors can't be presized ```java Map<K,V> m = items.stream().collect(Collectors.toMap(K, V)); ``` `Collectors.toMap` (and `toList`, `toSet`, `groupingBy`) create the container and **grow it dynamically** — there is no hook to pass an initial capacity for the standard collectors. For large, known-size streams this means the usual cascade of resizes. If it matters, collect into a pre-sized container yourself (e.g. a `for` loop into a `HashMap.newHashMap(n)`), or use the supplier overload (`toMap(km, vm, merge, () -> HashMap.newHashMap(n))`). `Collectors.toCollection(() -> new ArrayList<>(n))` lets you presize a list. ## Pitfall 4: power-of-two rounding HashMap capacity is always a power of two. Asking for capacity 1000 yields 1024; asking for 1025 yields 2048. So memory comes in chunks, and tiny differences in your requested capacity can double the allocation. Don't over-think the exact number, but be aware that `+1` in the idiom can occasionally push you over a power-of-two boundary. ## Pitfall 5: capacity is a hint, not a cap Initial capacity never limits how many elements you can add. Undershoot and the collection simply resizes as needed — you lose the optimization but nothing breaks. Overshoot and you waste memory. So a wrong estimate is a performance issue, never a correctness one. ## The clean tools - **Java 19+:** `HashMap.newHashMap(n)`, `HashSet.newHashSet(n)`, `LinkedHashMap.newLinkedHashMap(n)` — these take the *expected number of elements* and do the load-factor maths for you. Prefer them. - **Guava:** `Maps.newHashMapWithExpectedSize(n)`, `Lists.newArrayListWithCapacity(n)`. - **ArrayList:** `new ArrayList<>(n)` or `new ArrayList<>(collection)` is already correct (no load factor). ## Summary The recurring theme: don't pass the raw element count as a HashMap *capacity* — account for the 0.75 load factor, prefer the `newHashMap`/Guava helpers, remember stream collectors don't presize, and treat capacity as a non-binding hint.

  • Why is new ArrayList<>(otherList) safe to presize but new HashMap<>(n) often isn't?
    ArrayList has no load factor: the constructor allocates a backing array exactly the size needed, so copying a list never resizes. HashMap applies the 0.75 load factor on top of capacity, so a raw count as capacity leaves the map resizing at 0.75x that count — you must divide by the load factor or use newHashMap.
  • How would you presize a HashMap built via a stream of known size?
    Standard Collectors.toMap can't presize. Use the supplier overload, e.g. collect(Collectors.toMap(k, v, merge, () -> HashMap.newHashMap(n))), or skip the collector and loop the entries into a pre-sized HashMap.newHashMap(n) yourself.

saying these in an interview costs you the question

  • Believing new HashMap<>(n) guarantees n entries fit without resize
  • Assuming Collectors.toMap presizes from the stream's known size — it doesn't
  • Treating initial capacity as a maximum size limit
  • Forgetting power-of-two rounding when reasoning about memory
  • Applying the /0.75 idiom to ArrayList — it has no load factor

context