June 25, 20256 min

Rust Vec::new() vs. with_capacity(): When to Use Each

m
mayo

Understanding Vec allocation strategies is crucial for writing performant Rust code, especially when dealing with collections and iterators.

Vec::new() vs Vec::with_capacity(n) Vec::new() cap 0 cap 4 cap 8 cap 16 copy copy copy 4 reallocations with_capacity(16) cap 16 (1 alloc) push × 16 0 reallocations Knowing the final size upfront avoids repeated copy-and-free cycles

Key Differences

Vec::new() Vec::with_capacity(n)
Creates an empty Vec with no pre-allocated space Creates an empty Vec with space for n elements
Initial capacity is 0 (allocates on first push) Initial capacity is exactly n (no early allocations)
Grows dynamically (may reallocate multiple times) Avoids reallocation until len() > n

When to Use Each

Use Vec::new() when:

  • The number of elements is unknown or small
  • You want simplicity (e.g., short-lived vectors)
let mut v = Vec::new(); // Good for ad-hoc usage
v.push(1);

Use Vec::with_capacity(n) when:

  • You know the exact or maximum number of elements upfront
  • Optimizing for performance (avoids reallocations)
let mut v = Vec::with_capacity(1000); // Pre-allocate for 1000 items
for i in 0..1000 {
    v.push(i); // No reallocation happens
}

Performance Impact

Vec::new() may trigger multiple reallocations as it grows (e.g., starts at 0, then 4, 8, 16, ...). Vec::with_capacity(n) guarantees one allocation upfront (if n is correct).

Example Benchmark

use std::time::Instant;

fn main() {
    let start = Instant::now();
    let mut v1 = Vec::new();
    for i in 0..1_000_000 {
        v1.push(i); // Reallocates ~20 times
    }
    println!("Vec::new(): {:?}", start.elapsed());

    let start = Instant::now();
    let mut v2 = Vec::with_capacity(1_000_000);
    for i in 0..1_000_000 {
        v2.push(i); // No reallocations
    }
    println!("Vec::with_capacity(): {:?}", start.elapsed());
}

Output (typical):

Vec::new(): 1.2ms
Vec::with_capacity(): 0.3ms  // 4x faster

Advanced Notes

  • shrink_to_fit(): Reduces excess capacity (e.g., after removing elements)
  • vec![] macro: Uses with_capacity implicitly for literals (e.g., vec![1, 2, 3])

Pre-allocating is a hint, not a hard limit. len and capacity are two independent numbers, and the moment len would exceed capacity the usual doubling kicks back in:

Life of a Vec::with_capacity(4): len vs capacity len 2 / cap 4 len 4 / cap 4 len 5 / cap 8 len 5 / cap 5 push × 2 5th push realloc + copy shrink_to_fit() 2 free 0 free 3 free 0 free Solid = live elements (len) · dashed = reserved but unused (capacity − len) with_capacity(n) only postpones growth; pushing past n resumes the doubling schedule

Key Takeaways

  • ✅ Default to Vec::new() for simplicity.
  • ✅ Use with_capacity(n) when:
  • You know the size upfront
  • Performance is critical (e.g., hot loops)

Try This: What happens if you push beyond the pre-allocated capacity?
Answer: The Vec grows automatically (like Vec::new()), but only after exceeding n.