Sizing Hash Table Capacities Near Powers of Two

TL;DR / Summary: Many hash tables size their buckets to a power of two so index mapping can use bitwise masks instead of slow modulo operations. Before you hard-code 1024 or 4096, confirm 2ⁿ with tothepowerof2.com, plan a load-factor resize, and know when prime capacities are the wiser tradition.

Why buckets love 2ⁿ

A hash function yields a wide integer; the table must fold that integer into an index. If capacity is 2ⁿ, hash & (capacity - 1) replaces hash % capacity. On modern CPUs the mask is cheap and branch-friendly. Languages and libraries that promise amortized constant-time lookups often document power-of-two growth for that reason.

Growth policies commonly double: 16 → 32 → 64 → … Doubling preserves the power-of-two invariant and spreads entries after a rehash. Understanding the exponent helps you predict memory spikes when a table crosses a load-factor threshold mid-request. That prediction matters on latency-sensitive paths more than on batch jobs that can pause.

Load factor is the real dial

Capacity alone does not define performance. Load factor—entries divided by buckets—decides how often collisions chain or probe. A 2¹²-bucket table at 0.9 load can feel worse than a 2¹⁰ table at 0.5. When estimating memory, multiply expected entries by your max load factor, then round up to the next power of two so you do not resize during a latency-sensitive window.

Worked sketch: 1,800 session tokens, max load 0.75 → need at least 2,400 buckets → next power of two is 2¹² = 4096. Compute that ceiling with a calculator instead of guessing “about four thousand.” Write the assumption in a code comment so the next engineer knows why 4096 appeared rather than 3000.

When primes still appear

Some older guidance prefers prime bucket counts to reduce patterned collisions when hash mixing is weak. If your hasher is low quality or identity-based on sequential keys, a prime modulus can help. High-quality mixers plus power-of-two masks are the common modern pairing—especially in systems languages that control both hasher and table.

Read your standard library notes. Blindly forcing 2ⁿ in a structure that already rounds to primes wastes time and may fight its resize heuristics. Interop layers that wrap two different map types should not assume identical sizing philosophies.

Cache lines, allocator pressure, and servers

Doubling a large table briefly needs old and new storage. On a memory-tight VPS or mobile runtime, that spike matters more than the elegance of bitwise indexing. Pre-size tables when you know the cardinality (dictionaries of feature flags, caches of SKUs) so traffic does not trigger a mid-day rehash.

Also watch pointer-heavy entries: the bucket array may be 2ⁿ, but each collision node allocates separately. Profiling allocations often reveals more pain than the mask versus modulo debate. Sometimes a denser open-addressing table with a slightly larger power-of-two array beats a chaining design that looks smaller on paper.

Debugging checklist for “mysterious” slow maps

  • Log capacity and size at resize boundaries.
  • Verify the hasher is not returning near-sequential values for your key type.
  • Confirm you did not clamp capacity to a non-power accidentally via a buggy rounder.
  • Compare hit latency before and after a forced rehash offline.

If a custom round-up function fails on edge exponents, validate with known values: 2¹⁰=1024, 2¹⁶=65536, 2²⁰=1048576. Keep that cheat sheet in the PR description when changing defaults. Unit-test the rounder with inputs just below and above each boundary; off-by-one bugs love those edges.

Team conventions that prevent thrash

Agree whether configs express capacity as an exponent, as a raw bucket count, or as an expected entry count with a load factor. Mixing the three in one codebase produces silent production surprises. Prefer “expected entries + load factor” in product configs and let library code round to 2ⁿ internally. That keeps product managers away from bitwise trivia while still giving engineers predictable memory.

Benchmarks that answer the right question

Microbenchmarks that insert random keys into an empty map until a timer stops rarely resemble production. Prefer fixtures that match your key distribution: UUIDs, monotonic IDs, email strings, or composite keys. Measure lookup p95 after the table reaches steady size, including one forced resize, so you see allocation spikes.

Compare two capacities that differ by one exponent—say 2¹¹ versus 2¹²—while holding load factor constant. If latency barely moves but RSS jumps, the larger table may be vanity. If collision chains dominate profiles, the larger table may be cheap insurance. Publish both numbers in the PR so reviewers are not asked to trust folklore about “powers of two are always faster.”

For services with cold starts, pre-sizing to the expected steady cardinality can matter more than asymptotic elegance. A first-request rehash on a latency SLO path is a self-inflicted outage pattern.

Capacity near a power of two beats superstition

Power-of-two capacities are a performance convention, not a superstition. Size from expected entries and load factor, round up to a clean 2ⁿ when your library expects it, and use the powers-of-two tool to avoid off-by-one exponents that strand you at 2047 buckets instead of 2048. Predictable growth beats heroic micro-optimizations after an outage.