Skip to content
C++

std::vector — The Go-To Container

std::vector is a dynamically-sized array: it stores elements in contiguous memory (like a C array) but grows automatically as you add elements. It is the most-used container in the C++ standard library, and in most situations where you need a list of things, std::vector is the right choice. It offers fast random access by index, efficient appending at the end, and automatic memory management — no new or delete required.

Creating vectors

std::vector is a class template: you specify the element type in angle brackets. There are several ways to construct one.

#include <vector>

std::vector<int> empty;                      // empty vector, size 0
std::vector<int> scores = {85, 92, 78, 95}; // initializer list
std::vector<int> tenZeros(10, 0);            // 10 elements, all 0
std::vector<double> fives(5, 3.14);          // 5 copies of 3.14
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

std::cout << scores.size();   // 4 — number of elements
std::cout << scores.empty();  // false

Accessing elements

Elements are accessed by zero-based index, exactly like arrays. The [] operator does not bounds-check. The at() method throws std::out_of_range on invalid indices. front() and back() give the first and last elements.

std::vector<int> v = {10, 20, 30, 40, 50};

int first = v[0];         // 10
int last  = v[v.size()-1]; // 50
int front = v.front();    // 10
int back  = v.back();     // 50

// Modify an element:
v[2] = 99;   // {10, 20, 99, 40, 50}

// Bounds-checked access:
try {
    int oob = v.at(10);  // throws std::out_of_range
} catch (const std::out_of_range& e) {
    std::cout << "Out of range!\n";
}

Adding and removing elements

push_back() appends an element to the end. pop_back() removes the last element. emplace_back() constructs the element in-place (more efficient than constructing a temporary and pushing it). Inserting or erasing in the middle is possible but O(n) — all subsequent elements must be shifted.

std::vector<int> v;

v.push_back(10);   // {10}
v.push_back(20);   // {10, 20}
v.push_back(30);   // {10, 20, 30}

v.pop_back();      // {10, 20} — removes last element

// emplace_back constructs directly in the vector:
std::vector<std::string> words;
words.emplace_back("hello");   // slightly more efficient than push_back

// Insert at a position (expensive — O(n)):
v.insert(v.begin() + 1, 15);  // {10, 15, 20}

// Erase at a position (also O(n)):
v.erase(v.begin() + 1);       // {10, 20}

// Clear all elements:
v.clear();   // size becomes 0, capacity unchanged

Iterating over a vector

All three loop styles work with vectors. The range-based for loop is the cleanest for most use cases.

std::vector<int> scores = {85, 92, 78, 95, 61};

// Range-based for — cleanest:
for (int s : scores) {
    std::cout << s << " ";
}

// const ref — no copy, can't modify:
for (const auto& s : scores) {
    std::cout << s << " ";
}

// Index-based — use when you need the index:
for (std::size_t i = 0; i < scores.size(); ++i) {
    std::cout << i << ":" << scores[i] << " ";
}

// Modify each element:
for (auto& s : scores) {
    s += 5;   // adds 5 to every score in place
}

Size vs capacity — how growth works

A vector has two counts: size (the number of elements currently stored) and capacity (the number of elements the internal buffer can hold before the vector needs to reallocate). When you push_back and size reaches capacity, the vector allocates a new larger buffer (typically doubling), copies all elements over, and releases the old one. This is O(n) but happens rarely — amortized, each push_back is O(1).

std::vector<int> v;
std::cout << v.size() << "/" << v.capacity();   // 0/0

v.push_back(1);   // allocates buffer
v.push_back(2);
v.push_back(3);
std::cout << v.size() << "/" << v.capacity();   // 3/4 (typical)

// Reserve capacity up front to avoid reallocations:
std::vector<int> big;
big.reserve(1000);    // allocates space for 1000 elements now
for (int i = 0; i < 1000; ++i) {
    big.push_back(i); // no reallocation — capacity was pre-allocated
}

// Resize — changes the size (adds default-constructed elements if growing):
v.resize(6, 0);   // {1, 2, 3, 0, 0, 0}

Use reserve(n) when you know you will be adding approximately n elements — it avoids repeated reallocations and the associated copies.

Quick reference

OperationCode
Sizev.size()
Empty checkv.empty()
Access elementv[i] or v.at(i)
First / lastv.front() / v.back()
Appendv.push_back(x) or v.emplace_back(...)
Remove lastv.pop_back()
Insert at posv.insert(v.begin() + i, x)
Erase at posv.erase(v.begin() + i)
Clear allv.clear()
Pre-allocatev.reserve(n)
Resizev.resize(n, fill_value)
Pointer to datav.data() (raw array pointer)
Sortstd::sort(v.begin(), v.end())
Find elementstd::find(v.begin(), v.end(), val)

Key rules to remember

std::vector is the default container — reach for it unless you have a reason not to

Contiguous memory gives vector the best cache performance of any container. It outperforms linked lists for almost all practical workloads.

[] does not bounds-check — out-of-range access is undefined behavior

If the index might be invalid, use at() which throws instead of corrupting memory.

Use reserve() when you know the approximate final size

Pre-reserving capacity avoids repeated reallocations (each of which copies all elements) during a push_back loop.

Do not invalidate iterators or references while iterating

push_back may cause a reallocation that invalidates all existing iterators and pointers into the vector. Never push_back inside a range-for over the same vector.

Pass vector by const reference to avoid copying

void f(const std::vector<int>& v) avoids copying the entire buffer. The copy is O(n) — expensive for large vectors.

Sign in to track progress