Skip to content

Intermediate · The Standard Template Library (STL) · topic 21 of 42 · 30 min

std::vector in depth

Explanation

std::vector stores elements next to each other in memory and grows automatically. Adding at the end with push_back is fast (amortised O(1)), reading any index is O(1), but inserting or erasing in the middle is O(n). When a vector grows, it may move all elements, so old pointers, references and iterators to them become invalid.

Why vector is the default container

The STL (Standard Template Library) is the part of the C++ standard library that gives you containers (things that hold elements) and algorithms (things that work on them). Among the containers, std::vector is the one you should reach for first. Its elements sit in one continuous block of memory, exactly like an array, which makes it very fast for the processor's cache (a small, very fast memory that loves data stored side by side).

Size and capacity

size() is how many elements the vector holds. capacity() is how many it could hold before it must get a bigger block of memory. When you push_back into a full vector, it allocates a bigger block (typically 1.5× or 2× the old capacity), moves the elements across and frees the old block. Because growth is geometric, the average cost of push_back stays constant: this is called amortised O(1).

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;
    for (int i = 0; i < 6; ++i) {
        v.push_back(i);
        std::cout << "size " << v.size() << " capacity " << v.capacity() << '\n';
    }
    std::vector<int> orders;
    orders.reserve(100000);     // one allocation up front when you know the size
}
The exact capacities depend on the library (GCC doubles: 1, 2, 4, 4, 8, 8). reserve avoids repeated growth.
Think of it like this: A vector is like a family that moves to a bigger flat each time the current one fills up. Moving is expensive, but they always take a flat about twice as big, so they move rarely. reserve() is like buying the right-sized flat on day one when you already know the family size.

Common operations and their cost

OperationExampleCost
Read/write by indexv[i], v.at(i)O(1)
Add or remove at the endpush_back, emplace_back, pop_backamortised O(1)
Insert or erase in the middlev.insert(v.begin() + 2, x), v.erase(it)O(n): later elements shift
Search for a value (unsorted)std::find(v.begin(), v.end(), x)O(n)
First / last elementv.front(), v.back()O(1) (undefined if empty)

O(1) means the time does not grow with the number of elements; O(n) means it grows in proportion to n. You will use this notation (Big-O) for the rest of the course.

emplace_back

cpp
struct Order { std::string item; int qty; };
std::vector<Order> cart;
cart.push_back(Order{"Rice", 2});     // builds an Order, then moves it in
cart.emplace_back("Dal", 1);          // builds the Order directly inside the vector
emplace_back passes its arguments to the element's constructor. (For aggregates like this struct, that needs C++20.)

Iterator invalidation

Careful: When a vector reallocates (grows), every pointer, reference and iterator to its elements becomes invalid: they point to freed memory. A classic bug is int& first = v[0]; v.push_back(9); first = 5; which writes into freed memory. erase and insert also invalidate iterators at and after the change point. Re-read the element after changing the vector, or reserve enough space first.

Removing elements safely

cpp
std::vector<int> marks = {35, 80, 20, 90, 15};
// C++20: remove every value below 33
std::erase_if(marks, [](int m) { return m < 33; });     // marks is {80, 90}

// Before C++20: the erase-remove idiom
std::vector<int> v = {1, 0, 2, 0, 3};
v.erase(std::remove(v.begin(), v.end(), 0), v.end());   // v is {1, 2, 3}
std::remove only moves the kept elements to the front and returns the new logical end; erase then cuts off the rest.

Other useful operations

  • v.clear() removes all elements (capacity usually stays).
  • v.resize(n) changes the size, adding default values or dropping elements.
  • v.empty() is true when size() is 0; check it before front(), back() or v[0].
  • v.data() gives a pointer to the first element, for C functions.
  • Two vectors can be compared with == and < (element by element).
Tip: std::vector<bool> is a special, packed version that stores bits, not bools, and behaves oddly (you cannot take a bool& to an element). For flags, many programmers use std::vector<char> or std::bitset instead.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

std::vector in depth · 1/6

std::vector

std vector is the container you should use first. It stores elements side by side, like an array that grows.

Prefer a video by a teacher? Find free videos on “c++ std::vector size capacity reserve push_back iterator invalidation” on YouTube ↗

In real life: Collecting sensor readings

A factory machine sends a temperature reading every second, about 86,400 per day. The monitoring program reserves space for a day of readings, appends each one with push_back, and at midnight computes the maximum and clears the vector for the next day. One allocation per day instead of dozens.

cpp
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<double> readings;
    readings.reserve(86400);
    for (int s = 0; s < 10; ++s) readings.push_back(60.0 + s % 4);   // pretend data
    double maxTemp = *std::max_element(readings.begin(), readings.end());
    std::cout << "Max today: " << maxTemp << '\n';   // 63
    readings.clear();                                 // capacity is kept for tomorrow
}
reserve once, push_back many times, clear at the end of the day.

Practice (0/4 done)

Task 1 · Question

std::vector<int> v = {5, 6, 7}; v.pop_back(); v.push_back(9); What is v?

Task 2 · Question

Which operation on a vector of n elements is O(n)?

Task 3 · Question

Which line has the bug? 1: std::vector<int> v = {1, 2, 3}; 2: int* p = &v[0]; 3: for (int i = 0; i < 100; ++i) v.push_back(i); 4: std::cout << *p;

Task 4 · Practice

Read n exam marks into a vector. Remove all marks below 33 (fail), then print how many students passed and the marks in the original order. Use std::erase_if (C++20) or the erase-remove idiom.

Quick quiz

Question 1

1.What does reserve(1000) change?

Question 2

2.Why is push_back called "amortised O(1)"?

Question 3

3.What does std::remove(v.begin(), v.end(), 0) do on its own?

Question 4

4.Which container stores its elements in one continuous block of memory?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does reserve(1000) change?

Answer: capacity() becomes at least 1000; size() stays the same

reserve only pre-allocates memory; it does not add elements.

Why is push_back called "amortised O(1)"?

Answer: Occasional reallocations are spread over many cheap pushes because capacity grows geometrically

Doubling (or 1.5×) growth keeps the average cost per push constant.

What does std::remove(v.begin(), v.end(), 0) do on its own?

Answer: Moves non-zero elements to the front and returns the new logical end; the size is unchanged

You must call erase with the returned iterator to actually shrink the vector.

Which container stores its elements in one continuous block of memory?

Answer: std::vector

vector (and array) are contiguous; list and map use separate nodes.

What is the main advantage of std::vector over a C-style array?Related

Answer: Its size can change at run time and it knows its size

vector grows with push_back and reports size(); it also manages its own memory.

From Arrays, std::array and a first look at std::vector

What does it->first give for a std::map iterator?Related

Answer: The key

Map elements are pairs: first is the key, second is the value.

From Iterators: the glue between containers and algorithms

Starting from capacity 4 and doubling, what is the capacity after 9 pushes?Related

Answer: 16

4 → 8 → 16.

From realloc and growing arrays

Why is list.append O(1) amortised and not O(1) every time?Related

Answer: Occasionally the list is full and must be copied to a bigger block

Most appends use spare room; a few trigger an O(n) copy. Averaged over many appends, the cost is O(1).

From Space complexity and amortised cost

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “The Standard Template Library (STL)” before its exam: std::map and std::unordered_map, set, stack, queue, deque and priority_queue, Iterators: the glue between containers and algorithms, STL algorithms: sort, find, count, accumulate and binary search.
  2. Module 6 exam: The STL · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Templates, smart pointers and move semantics. 42 topics left in the course before the final exam.

← Previous: Abstract classes, interfaces and good class design