LearnFrenzy
Sign in Take a free test

STL: Vector and Sequence Containers · Lesson 17 of 33

Vector in C++ STL

6 min read Updated Nov 7, 2026 Saurabh Samir

std::vector is a dynamic array: contiguous in memory like a C array, but it knows its size, grows automatically and frees itself. It's the container you'll use most – in contests it replaces almost every array – so it's worth knowing deeply.

In this lesson

  • The STL in one picture
  • Creating, reading and growing vectors
  • size vs capacity, and reserve
  • insert/erase, 2D vectors and common mistakes

The STL in one picture

The Standard Template Library

STL

  • Containersvector, array, deque, list, set, map, unordered_map, stack, queue, priority_queue
  • Iteratorsthe "pointers" that walk through any container
  • Algorithmssort, find, count, binary_search, accumulate… work on iterator ranges
  • Function objectslambdas and comparators that customise algorithms

Algorithms don't know about containers – they work on iterator ranges. That's why one std::sort sorts vectors, arrays, deques and strings.

Creating and using vectors

#include <iostream>
#include <vector>

void print(const std::vector<int>& v) {
    for (int x : v) std::cout << x << ' ';
    std::cout << "(size " << v.size() << ")\n";
}

int main() {
    std::vector<int> a;                    // empty
    std::vector<int> b(5);                 // five zeros
    std::vector<int> c(4, 7);              // four sevens
    std::vector<int> d{3, 1, 4, 1, 5};     // from a list
    print(a); print(b); print(c); print(d);

    d.push_back(9);                        // add at the end
    d.pop_back();                          // remove from the end
    d[0] = 30;                             // no bounds check
    std::cout << d.front() << ' ' << d.back() << ' ' << d.at(2) << '\n';   // at() checks bounds
    std::cout << std::boolalpha << a.empty() << '\n';
    return 0;
}

Output

(size 0)
0 0 0 0 0 (size 5)
7 7 7 7 (size 4)
3 1 4 1 5 (size 5)
30 5 4
true

( ) vs { }

std::vector<int> v(5, 2); is five 2s, but std::vector<int> v{5, 2}; is two elements: 5 and 2. Braces always mean "these values".

size, capacity and reserve

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;
    std::size_t last_cap = v.capacity();
    for (int i = 0; i < 20; i++) {
        v.push_back(i);
        if (v.capacity() != last_cap) {             // the vector reallocated
            last_cap = v.capacity();
            std::cout << "size " << v.size() << " -> capacity " << last_cap << '\n';
        }
    }
    std::vector<int> w;
    w.reserve(1000);                                // allocate once up front
    std::cout << "reserved: size " << w.size() << ", capacity " << w.capacity() << '\n';
    return 0;
}

Output

size 1 -> capacity 1
size 2 -> capacity 2
size 3 -> capacity 4
size 5 -> capacity 8
size 9 -> capacity 16
size 17 -> capacity 32
reserved: size 0, capacity 1000

size() is how many elements exist; capacity() is how many fit before the vector must reallocate. When full, it allocates a bigger block (here doubling) and moves the elements, so push_back is O(1) on average. If you know the final size, reserve avoids the reallocations. (Growth factors differ between library implementations.)

insert, erase and emplace_back

#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
    std::vector<int> v{10, 20, 30, 40, 50};
    v.insert(v.begin() + 2, 25);                 // insert before index 2
    v.erase(v.begin());                          // erase the first element
    v.erase(v.begin() + 1, v.begin() + 3);       // erase a range [1, 3)
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';

    std::vector<int> marks{45, 72, 38, 90, 33, 61};
    std::erase_if(marks, [](int m) { return m < 40; });   // C++20: remove all failing marks
    for (int m : marks) std::cout << m << ' ';
    std::cout << '\n';

    std::vector<std::pair<std::string, int>> scores;
    scores.emplace_back("Asha", 91);             // builds the pair in place
    scores.push_back({"Ravi", 74});              // builds a temporary, then moves it
    for (const auto& [name, s] : scores) std::cout << name << '=' << s << ' ';
    std::cout << '\n';
    return 0;
}

Output

20 40 50 
45 72 90 61 
Asha=91 Ravi=74 
OperationCost
v[i], v.at(i)O(1)
push_back / pop_backO(1) amortised
insert / erase in the middleO(n) – later elements shift
search by valueO(n), or O(log n) if sorted (binary search)

2D vectors

#include <iostream>
#include <vector>

int main() {
    int rows = 3, cols = 4;
    std::vector<std::vector<int>> grid(rows, std::vector<int>(cols, 0));   // 3 x 4 zeros
    for (int r = 0; r < rows; r++)
        for (int c = 0; c < cols; c++) grid[r][c] = r * cols + c;

    for (const auto& row : grid) {
        for (int x : row) std::cout << x << '\t';
        std::cout << '\n';
    }

    std::vector<std::vector<int>> adj(4);        // adjacency list: rows of different lengths
    adj[0] = {1, 2};
    adj[2] = {3};
    for (std::size_t u = 0; u < adj.size(); u++) std::cout << u << " has " << adj[u].size() << " neighbours\n";
    return 0;
}

Output

0	1	2	3	
4	5	6	7	
8	9	10	11	
0 has 2 neighbours
1 has 0 neighbours
2 has 1 neighbours
3 has 0 neighbours

Common vector mistakes

  • Iterator invalidation: after push_back causes a reallocation, or after insert/erase, old iterators, pointers and references into the vector may dangle.
  • Erasing inside a range-for loop – use std::erase_if instead.
  • v.size() - 1 when v is empty wraps around (size is unsigned).
  • Passing vectors by value to functions – copies everything; pass by const&.

Practice

Try it yourself

  1. Build a vector of the first 10 squares, then remove the odd ones with erase_if and print the rest.

    Show solution
    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<int> sq;
        sq.reserve(10);
        for (int i = 1; i <= 10; i++) sq.push_back(i * i);
        std::erase_if(sq, [](int x) { return x % 2 != 0; });
        for (int x : sq) std::cout << x << ' ';
        std::cout << '\n';
        return 0;
    }

    Output

    4 16 36 64 100 

Quick check

size() vs capacity()?

size is the number of elements; capacity is how many fit before reallocation.

What is the cost of push_back?

O(1) amortised.

Why can iterators become invalid after push_back?

If the vector reallocates, its elements move to new memory.

Summary

  • vector = contiguous dynamic array: O(1) indexing and push_back, O(n) middle insert/erase.
  • reserve when the size is known; use erase_if to remove by condition.
  • 2D vectors and adjacency lists are vectors of vectors; beware iterator invalidation.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular