Capacity and Performance
Because HashMap<K, V> and HashSet<T> are open-addressed hash tables, their lookup speed and memory footprint depend on load factor, table resizing (rehashing), tombstone cleanup, and key/value inline size.
Power-of-Two Bucket Sizing and the 87.5% Load Factor
Internally, HashMap always allocates a power-of-two number of raw buckets ($4, 8, 16, 32, \dots, 2^k$) so bucket indexing uses a single bitwise AND (h1 & (raw_buckets - 1)) instead of slow integer division (%).
To keep probe sequences short, HashMap enforces a maximum load factor of $7/8$ (87.5%):
$$\text{usable capacity} = \text{raw buckets} \times \frac{7}{8}$$
Requested Elements (with_capacity(n)) |
Raw Buckets Allocated ($2^k$) | Usable .capacity() ($2^k \times 7 / 8$) |
|---|---|---|
0 |
0 |
0 |
1..=3 |
4 |
3 |
4..=7 |
8 |
7 |
8..=14 |
16 |
14 |
15..=28 |
32 |
28 |
29..=56 |
64 |
56 |
What Happens During a Rehash
When inserting a new element would exceed the $7/8$ load factor (growth_left == 0), HashMap rehashes:
- Allocates a new table with double the raw buckets ($2^{k+1}$).
- Recomputes
key.hash(&mut hasher)for every existing element in the old table and inserts(K, V)into its new bucket position. - Frees the old table.
Unlike Vec<T> reallocation (which is a single contiguous memcpy), rehashing a HashMap must re-hash every key and scatter elements into new bucket offsets. Pre-allocating with HashMap::with_capacity(expected_len) or map.reserve(additional) avoids intermediate rehashes completely:
use std::collections::HashMap;
fn main() {
// Pre-allocates a 1024-bucket table (usable capacity = 896) in one allocation:
let mut map: HashMap<u32, u32> = HashMap::with_capacity(500);
let initial_cap = map.capacity();
for i in 0..500 {
map.insert(i, i * 2);
}
assert_eq!(map.capacity(), initial_cap);
println!("Inserted 500 items with 0 rehashes (capacity = {initial_cap})");
}
Inserted 500 items with 0 rehashes (capacity = 896)
Inline Bucket Size and Large Values (Box<V>)
Because SwissTable stores (K, V) pairs directly inline in the bucket array (and leaves $1/8$ of buckets empty as load-factor slack), storing a very large struct V (for example, [u8; 4096]) wastes 4 KiB per empty slot and hurts cache locality during rehashing. When V is hundreds of bytes and the map is sparse or frequently resized, wrapping the value in Box<V> (HashMap<K, Box<LargeStruct>>) keeps each bucket down to an 8-byte pointer.