SysTutorials

Why std::vector Outperforms Other C++ Data Structures

· updated April 11, 2026

std::vector is the right default choice for almost every container need in C++. It’s the foundation of high-performance applications—game engines, financial systems, data processing pipelines—because it aligns with how modern CPUs actually work.

Cache Locality Drives Performance

std::vector stores elements in contiguous memory. When you iterate, the CPU prefetches the next elements into L1/L2 cache, giving you predictable, fast access patterns. Competing containers scatter data across the heap.

Compare iteration performance:

A simple iteration benchmark on modern hardware shows std::vector can be 10-20x faster than std::list for the same operation, purely because of cache behavior. CPU prefetchers work best with linear access patterns.

Amortized O(1) Growth

When you push_back() to a full vector, it reallocates—doubling capacity, copying elements to the new block. This sounds expensive, but it happens rarely enough that the amortized cost per insertion is still O(1). If you insert n elements, you do O(n) total work, not O(n²).

The growth factor matters. Most standard libraries use factor 2, which gives you ~log(n) reallocations for n insertions. Some embedded or resource-constrained environments tune this differently, but 2 is the safe default.

Practical Optimization Patterns

Reserve when you know the size:

std::vector<Item> items;
items.reserve(10000);  // Allocate once, no reallocations during growth
for (auto& source : data_source) {
    items.push_back(source);
}

Calling reserve() once prevents repeated allocations. If you don’t know the final size, the doubling strategy still works—just less efficiently.

Reclaim memory after heavy deletion:

std::vector<Record> records = load_large_dataset();
// ... filter down to 1% of original size
records.erase(remove_if(records.begin(), records.end(), predicate), records.end());
records.shrink_to_fit();  // Return excess capacity to the OS

shrink_to_fit() triggers a reallocation to exact size. Use it when you’ve permanently reduced the dataset, not in tight loops.

Move semantics for zero-copy returns:

std::vector<Data> process_data() {
    std::vector<Data> result;
    result.reserve(expected_size);
    // ... populate result
    return result;  // RVO/move elision, no copy
}

Modern compilers apply return value optimization (RVO) and move semantics automatically. Your vector escapes the function without copying.

When to Choose Something Else

std::vector isn’t always optimal. Use alternatives only when you have specific requirements:

Don’t use std::list or std::deque “just in case”—profiling shows vector wins in the vast majority of cases.

Modern CPU Architecture Matters

Recent CPUs (Zen 5, Sapphire Rapids, ARM’s latest) have larger caches and better prefetchers, making contiguous memory even more important. Speculative execution, SIMD operations, and vectorization instructions all favor the linear access patterns std::vector provides.

The old wisdom remains: Bjarne Stroustrup’s recommendation to “use vector by default, another container only with specific reason not to” is more relevant in 2026 than it was 20 years ago.

← All posts