18.4Vec: capacity and growth
A Vec grows when you push, but it doesn't reallocate on every single push, that would be wasteful. Understanding the difference between a vector's length and its capacity explains how growth actually works, why it's efficient, and how to make it even more efficient when you know roughly how many elements you'll need. This is a short, practical lesson about the machinery under push.
Length versus capacity
A Vec tracks two numbers. Length (len()) is how many elements it currently holds. Capacity (capacity()) is how many it could hold before needing to allocate a bigger heap buffer. Capacity is always at least the length, and usually larger:
fn main() {
let mut v = Vec::new();
println!("len {}, cap {}", v.len(), v.capacity());
for i in 1..=5 {
v.push(i);
println!("pushed {i}: len {}, cap {}", v.len(), v.capacity());
}
}len 0, cap 0
pushed 1: len 1, cap 4
pushed 2: len 2, cap 4
pushed 3: len 3, cap 4
pushed 4: len 4, cap 4
pushed 5: len 5, cap 8
(The exact capacity numbers are an implementation detail and could differ between Rust versions, but the pattern is what matters.) Watch what happens: the empty vector has capacity 0. The first push allocates a buffer with room for 4. Pushes 2 through 4 fit in that room, so the capacity stays 4 and no allocation happens. The fifth push exceeds capacity 4, so the vector allocates a bigger buffer (here doubling to 8), copies the existing elements over, and adds the new one. Length grew by one each time; capacity jumped only when the buffer filled.
Why growth is amortized-cheap
This doubling strategy is why push is fast on average even though it occasionally does expensive work. Most pushes just write into already-allocated space (cheap). Once in a while, a push fills the buffer and triggers a reallocation: a new, larger buffer is allocated and all existing elements are copied to it (lesson 8.2's heap allocation, plus a copy). Because the capacity doubles each time, these expensive reallocations get rarer as the vector grows, so the cost spread across all pushes stays low. (This connects to the iteration rule from lesson 18.3: a reallocation moves the elements to a new address, which is exactly why holding a reference into the vector while pushing would dangle, and why the borrow checker forbids it.)
Key insight
Length is what's in the vector; capacity is how much room is allocated. push writes into spare capacity when there is any, and reallocates a larger buffer (copying everything) when there isn't. The doubling-on-growth strategy makes the occasional expensive reallocation rare enough that pushes are cheap on average. You usually don't think about capacity at all, it just works, but knowing it explains both why push is fast and why pushing can invalidate references into the vector.
with_capacity: pre-allocating
When you know roughly how many elements you'll add, you can skip the repeated reallocations by allocating enough room up front with Vec::with_capacity(n):
fn main() {
let mut v = Vec::with_capacity(100);
println!("len {}, cap {}", v.len(), v.capacity()); // len 0, cap 100
for i in 0..100 {
v.push(i); // no reallocation
}
println!("len {}, cap {}", v.len(), v.capacity()); // len 100, cap 100
}len 0, cap 100
len 100, cap 100
Vec::with_capacity(100) creates an empty vector (length 0) with room for 100 elements already allocated. Now those 100 pushes all fit in the pre-allocated buffer, so not one of them reallocates. For a loop that adds a known number of items, this is a small, free performance win: one allocation instead of several, and no copying as it grows. It's the same idea as reserving a table for the number of guests you expect, rather than adding chairs one at a time.
Best practice
Reach for Vec::with_capacity(n) when you know (even approximately) how many elements you're about to add, especially in a hot loop building a large vector. It avoids repeated reallocation and copying. When you don't know the size, plain Vec::new() (or vec![]) is fine, the doubling strategy keeps it efficient. Don't over-think capacity for small or short-lived vectors; it matters most when building big ones in performance-sensitive code.
Quiz time
Question #1
What's the difference between a vector's length and its capacity?
Show solution
Length (len()) is how many elements the vector currently holds; capacity (capacity()) is how many it can hold before it must allocate a larger heap buffer. Capacity is always at least the length and is often larger, representing pre-allocated spare room that pushes can fill without reallocating.
Question #2
What happens when you push onto a vector whose length equals its capacity?
Show solution
It reallocates: the vector allocates a new, larger buffer (typically double the capacity), copies all existing elements to it, then adds the new element. This is occasionally expensive, but because capacity doubles, reallocations become rarer as the vector grows, keeping the average push cost low. The reallocation moves elements to a new address, which is why references into the vector can't be held across a push.
Question #3
You're about to build a vector of exactly 10,000 elements in a loop. How can you avoid repeated reallocations, and why does it help?
Show solution
Use Vec::with_capacity(10_000), which allocates room for all 10,000 elements up front (length still 0). Then the 10,000 pushes all fit in the pre-allocated buffer with no reallocation or copying along the way, one allocation instead of many. It's a small free win when the size is known ahead of time.
Vec holds any type. The next lesson zooms in on the most-used specialized vector of all: String, examined as the collection of UTF-8 bytes it really is, which finally explains why s[0] doesn't compile and why string slicing can panic.