skip to content

Before bulk-loading five million records into a hash table, how do you choose its initial capacity?

level: middleimportance: should knowfreq 52%

answer

  1. the threshold is a fraction of capacity
  2. n buckets for n entries is too full
  3. divide before you allocate
  4. expected entries over target load factor
  5. check whether the knob means buckets or entries

basics

~20 s

Divide the expected entry count by the target load factor and round up: five million records at a 0.75 growth threshold needs about 6.7 million buckets. Sizing to five million buckets exactly still triggers a resize.

solid answer

~50 s

The formula is `buckets >= expected_entries / target_load_factor`, rounded up — and rounded to the next power of two if the implementation requires one. For five million records at a 0.75 growth threshold that is 6,666,667 buckets, which rounds to 8,388,608. The trap is sizing to the entry count itself: five million entries in five million buckets sits at load factor 1.0, well past the threshold, so the table resizes anyway during the load. The second trap is the knob's units — some interfaces take an expected *entry* count and apply the load factor for you, others take a raw bucket count, and confusing them wastes memory by a third or leaves the final resize in place. Skipping this costs about nineteen resizes and roughly 6.3 million redundant entry moves on top of the five million placements you actually wanted.

go deeper

for a junior

Remember that a table grows before it is full, so the capacity you ask for must be bigger than the number of entries you plan to store. Know the divide-by-the-load-factor step.

for a middle

Compute it: expected entries over target load factor, rounded to an accepted capacity, and be able to state roughly how many resizes and entry moves the default-sized path would have performed instead.

for a senior

Weigh the other side — permanent memory for a transient peak, sparser cache behaviour, and the fact that pre-sizing cures resize pauses but not collision clustering or unbounded key growth.

for a principal

Decide when pre-sizing belongs in the code at all versus being a workload assumption that will silently rot. Own the call between sizing for peak, bounding the table, and leaving the default in place.

## The arithmetic A table resizes when `entries > load_factor x capacity`. To avoid ever crossing that line while loading n entries, you need `capacity >= n / load_factor` For a batch import of 5,000,000 catalog records into a table that grows at 0.75: `5,000,000 / 0.75 = 6,666,667 buckets` If the implementation requires a power-of-two capacity — common, because it lets the index reduction be a bit-mask instead of a division — round up to **8,388,608**. That is 1.68x the entry count in slot count, which is the price of never rehashing during the load. ## What skipping it costs Starting from a typical small default of 16 buckets, the table doubles until it can hold five million entries: 16 → 32 → … → 8,388,608, which is nineteen resizes. The entries moved at each resize is the count present at the time, about 0.75 of the capacity being outgrown, so the total is `0.75 x (16 + 32 + … + 4,194,304) ≈ 6.3 million entry moves` That is more re-placement work than the actual insertion work, plus nineteen allocations of ever-larger arrays, nineteen periods where two arrays are simultaneously live (the largest pair peaking near 12.5 million slots), and the memory churn all of that leaves behind. Pre-sizing removes every bit of it and replaces it with one allocation. ## The two mistakes **Sizing to the entry count.** Requesting five million buckets for five million entries puts the table at load factor 1.0, far past any mainstream growth threshold. It will resize at least once during the load — usually near the end, when the transfer is at its most expensive. Whatever number you compute, divide by the load factor before you pass it. **Getting the units wrong.** Container interfaces differ in what their sizing parameter means. Some take a raw bucket/capacity count; others take an *expected element count* and apply the load factor internally; a few take a target load factor as a second parameter. Passing an entry count to a bucket-count parameter leaves a resize in flight; passing an already-divided bucket count to an entry-count parameter over-allocates by another 1/load_factor. Read what the knob means before you set it — this is the single most common way a well-intentioned pre-size does nothing. ## When to stop over-sizing Pre-sizing is not free, and "round generously up" is not the right instinct: - **Memory.** Buckets cost space whether or not they hold anything, and most tables never shrink on their own. A table sized for a peak that arrives once holds that footprint for the life of the process. - **Cache and iteration.** A very sparse table spreads live entries across more cache lines, so lookups miss more often and a full iteration walks many empty slots. Under open addressing this is especially visible, since iteration is a linear scan of the whole slot array. - **Unknown n.** If you genuinely cannot estimate the final size, a bad guess is worse than the default: guess low and you keep the resizes anyway, guess high and you pay memory forever. Estimate from a real signal — a row count, a file size, a prior run — or don't pre-size. ## What pre-sizing does not fix It removes resize pauses. It does **not** improve the per-operation collision behaviour: a poor hash function that clusters keys still produces long chains or long probe sequences in a large table, because clustering is about how the hash spreads, not about how much room it has. Nor does it help a table whose growth is driven by unbounded key churn — a table that keeps accumulating new keys will eventually pass whatever capacity you chose. In that case the right answer is a bounded structure with eviction or expiry, not a bigger initial allocation. ## How to say it in an interview "Expected entries divided by the growth threshold, rounded up to the implementation's accepted capacity — so about 6.7 million buckets for five million records at 0.75, which rounds to 8.4 million if powers of two are required. I'd check whether the sizing parameter means buckets or expected entries before trusting it, and I'd verify by watching the table's capacity after the load rather than assuming the pre-size took."

  • Why is sizing the table to exactly five million buckets for five million entries still wrong?
    It puts the table at load factor 1.0, well past the roughly 0.6-0.75 threshold mainstream tables grow at, so a resize fires during the load — typically near the end, when the transfer moves the most entries. The capacity you request has to be the entry count divided by the load factor, not the entry count itself.
  • How would you verify that the pre-size actually took effect?
    Read the table's capacity or bucket count after the load and confirm it equals the value you asked for rather than a doubled one, or count resize events with instrumentation. Sizing parameters are easy to misread — some mean buckets, some mean expected entries — so verifying beats assuming, especially when the load is the hot path you were optimising.
  • When is refusing to pre-size the better call?
    When the final size is genuinely unknown and no cheap signal estimates it, or when the peak is transient and the process is memory-constrained — a table sized for peak keeps that footprint, since most implementations never shrink. A wrong high guess costs memory permanently while a wrong low guess buys nothing, so with no estimate the default growth path is the honest choice.

saying these in an interview costs you the question

  • Sizes the table to the expected entry count directly
  • Ignores which units the sizing parameter expects
  • Assumes over-sizing is free because memory is cheap
  • Thinks pre-sizing also fixes a poorly distributing hash function
  • Expects the table to shrink back after the peak passes

context