C++ · Intermediate

Vectors in C++ (std::vector)

11 min readUpdated September 24, 2026Every example verified

In short: std::vector is the standard library's growable array: it owns a contiguous block of elements, push_back appends, size() reports the count, [] and at() index it, and it can be passed by reference, iterated with a range-for and nested to make grids. It is the default container for a sequence whose length is decided at run time.

An array that grows

A built-in array has a length fixed when it is declared, so a program reading an unknown number of values must guess a maximum. std::vector<T>, from <vector>, removes the guess: it starts empty or at a chosen size and grows as you push_back elements, keeping them in one contiguous block just like an array. Indexing with v[i] is as fast as array indexing, and because a vector knows its own size(), a function receiving one needs no separate length parameter.

Growth is automatic. When the block is full, the vector allocates a larger one, moves the elements across and frees the old block; the new block is larger by a factor rather than by one, so the average cost of a push_back stays constant. That reallocation is also the vector's one trap: any pointer, reference or iterator into the old block is invalid afterwards. If you know roughly how many elements are coming, reserve(n) allocates once up front.

v[i] does no bounds checking: an index past the end reads or writes memory you do not own, with no error message. v.at(i) checks and throws std::out_of_range, worth its small cost when the index comes from input. front() and back() give the first and last element, empty() says whether there are any, and pop_back() removes the last. insert and erase take an iterator position such as v.begin() + i; elements after that point shift, so these cost time proportional to the distance to the end and suit small vectors or positions near the back.

Indexes and size() have type std::size_t, an unsigned integer. Comparing it with a signed int counter draws a warning under -Wall, so declare counters as std::size_t or use a range-for, for (int n : boarded), which cannot run out of range. Write for (const std::string& w : words) to avoid copying each element and for (int& x : v) to modify in place.

A vector passed by value is copied, every element included. Pass const std::vector<T>& to read and std::vector<T>& to modify. A vector of vectors, std::vector<std::vector<char>>, makes a grid; the constructor form seats(rows, std::vector<char>(cols, '.')) builds one already filled. Sorting and searching a vector are the subject of STL algorithms.

Syntax

 C++ · syntax
#include <vector>

std::vector<int> a;                 // empty
std::vector<int> b(5);              // five zeros
std::vector<int> c(5, 7);           // five sevens
std::vector<int> d{5, 7};           // two elements: 5 and 7

a.push_back(12);                    // append
a.pop_back();                       // remove the last element
a.size();  a.empty();               // count; whether there are none
a[0];  a.at(0);                     // unchecked / checked access
a.front();  a.back();
a.insert(a.begin() + 1, 99);        // insert before index 1
a.erase(a.begin());                 // remove index 0
a.resize(10);                       // new elements are value-initialised (0)
a.clear();                          // remove everything

for (int x : a) { /* read each */ }
for (int& x : a) { x *= 2; }        // modify in place
void total(const std::vector<int>& v);   // pass by const reference

std::vector<int> b(5) and std::vector<int> d{5} are different: parentheses give a count, braces give the elements.

Reading values into a vector and walking it

Passengers boarding at each stop. The count comes first, then the values, and the vector grows one push_back at a time.

 C++
#include <iostream>
#include <vector>

int main() {
    int stops;
    std::cin >> stops;
    std::vector<int> boarded;             // starts empty, size 0
    for (int i = 0; i < stops; ++i) {
        int n;
        std::cin >> n;
        boarded.push_back(n);             // grows by one element
    }
    int total = 0;
    for (int n : boarded) total += n;     // range-for visits each element
    std::cout << "stops: " << boarded.size() << "\n";
    std::cout << "first: " << boarded.front() << ", last: " << boarded.back() << "\n";

    std::size_t best = 0;
    for (std::size_t i = 1; i < boarded.size(); ++i) {
        if (boarded[i] > boarded[best]) best = i;
    }
    std::cout << "busiest stop: index " << best << " with " << boarded[best] << "\n";
    std::cout << "average: " << static_cast<double>(total) / boarded.size() << "\n";
    return 0;
}

Input given to the program: 512 7 19 4 8

Output

stops: 5
first: 12, last: 8
busiest stop: index 2 with 19
average: 10

boarded starts empty and ends with five elements, so no maximum had to be guessed. The range-for reads every element for the total; the indexed loop is used where the index itself matters, and best is a std::size_t to match size(). Casting total to double before dividing avoids integer division, though here the average happens to be whole.

Passing a vector to functions, erasing and checked access

Tags read one per line until the input ends. One function reads the vector through a const reference; another removes short tags through a non-const reference.

 C++
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

int longest(const std::vector<std::string>& words) {     // read only, no copy made
    std::size_t best = 0;
    for (const std::string& w : words) {
        if (w.size() > best) best = w.size();
    }
    return static_cast<int>(best);
}

void dropShort(std::vector<std::string>& words, std::size_t minLen) {  // changes the caller's vector
    for (std::size_t i = 0; i < words.size(); ) {
        if (words[i].size() < minLen) {
            words.erase(words.begin() + i);   // later elements shift down: do not advance i
        } else {
            ++i;
        }
    }
}

int main() {
    std::vector<std::string> tags;
    std::string line;
    while (std::getline(std::cin, line)) {
        if (!line.empty()) tags.push_back(line);
    }
    std::cout << "longest tag: " << longest(tags) << " chars\n";
    dropShort(tags, 4);
    tags.insert(tags.begin(), "all");         // insert at the front
    for (std::size_t i = 0; i < tags.size(); ++i) {
        std::cout << i << ": " << tags[i] << "\n";
    }
    try {
        std::cout << tags.at(10) << "\n";     // at() checks the index
    } catch (const std::out_of_range&) {
        std::cout << "no element at 10\n";
    }
    return 0;
}

Input given to the program: salenewveganhotgluten-free

Output

longest tag: 11 chars
0: all
1: sale
2: vegan
3: gluten-free
no element at 10

longest cannot modify words and makes no copy. dropShort erases in place: after an erase the next element has moved into index i, so i is only advanced when nothing was removed. insert at begin() shifts every element up by one. at(10) on a four-element vector throws std::out_of_range, which the catch turns into a message; tags[10] would have been undefined behaviour with no message at all.

A two-dimensional vector

A seating chart built as a vector of rows, each row a vector of characters. Bookings mark seats with X.

 C++
#include <iostream>
#include <vector>

int main() {
    int rows, cols;
    std::cin >> rows >> cols;
    // rows vectors, each holding cols copies of '.'
    std::vector<std::vector<char>> seats(rows, std::vector<char>(cols, '.'));

    int bookings;
    std::cin >> bookings;
    for (int i = 0; i < bookings; ++i) {
        int r, c;
        std::cin >> r >> c;
        seats[r][c] = 'X';
    }
    int free = 0;
    for (const std::vector<char>& row : seats) {
        for (char s : row) {
            std::cout << s;
            if (s == '.') ++free;
        }
        std::cout << "\n";
    }
    std::cout << free << " free of " << rows * cols << "\n";
    return 0;
}

Input given to the program: 3 430 11 32 0

Output

.X..
...X
X...
9 free of 12

The constructor seats(rows, std::vector<char>(cols, '.')) creates rows copies of a row that already holds cols dots, so every seat exists before any booking is read. seats[r][c] indexes the row first, then the column. The nested range-for takes each row by const& to avoid copying it and each seat by value.

Operations and their cost

OperationEffectCost
push_back(x)append at the endconstant on average
pop_back()remove the last elementconstant
v[i], v.at(i)element i; at() throws on a bad indexconstant
insert(pos, x), erase(pos)shift every later elementlinear in the elements after pos
size(), empty()count; whether there are noneconstant
clear()remove every elementlinear

Common mistakes

  • Indexing past the end with []

    Why it goes wrong: v[v.size()] is one past the last element. There is no check, so the program reads or writes memory it does not own and may print garbage or crash later, far from the bug.

    Fix: Keep indexes below size(), or use at() where the index comes from input.

     C++ · fix
    if (i < v.size()) std::cout << v[i];
  • Erasing elements inside a range-for

    Why it goes wrong: erase invalidates iterators into the vector, including the hidden one driving the range-for, so the loop continues over invalid memory.

    Fix: Use an index loop that advances only when nothing was erased, or the remove-erase idiom from the algorithms lesson.

  • Keeping a pointer or reference to an element across a push_back

    Why it goes wrong: A push_back that triggers reallocation moves every element to a new block, so int& first = v[0] now refers to freed memory.

    Fix: Hold indexes instead of references while the vector is still growing, or call reserve first.

  • Passing a vector by value when it only needs to be read

    Why it goes wrong: Every element is copied on each call; for a vector of a million entries that dominates the run time and is easy to miss because the code is correct.

    Fix: Take const std::vector<T>&.

     C++ · fix
    int longest(const std::vector<std::string>& words);

Where you use this

A vector is the first thing to reach for whenever a program collects values: readings from a file whose length is unknown, the results of a query, the lines of a document, the neighbours of a node in a graph. push_back and pop_back at the end also make it a perfectly good stack, and a vector of vectors is the usual representation of a grid, a matrix or an adjacency list.

It is the container most other library pieces assume. STL algorithms sort and search it through iterators, maps often hold a vector as their value type to group items under a key, and std::priority_queue is built on top of one.

 C++ · in practice
std::map<std::string, std::vector<std::string>> byCategory;
byCategory[category].push_back(itemName);   // group items under a key

Key points

  • std::vector<T> is a contiguous, growable array; push_back appends and size() reports the count.
  • [] is unchecked; at() throws std::out_of_range.
  • Use std::size_t for indexes, or a range-for; take elements by const& to avoid copies.
  • Pass vectors by const& to read and by & to modify; passing by value copies everything.
  • Reallocation on growth invalidates pointers, references and iterators into the vector.
  • std::vector<std::vector<T>> builds grids; v(rows, std::vector<T>(cols, value)) fills one.

Try it yourself

The program reads a count and that many integers into a vector and prints them in order. Change the printing loop so the values come out in reverse order, separated by single spaces, so the input 3 8 1 6 prints 6 1 8 3.

Your program
#include <iostream>
#include <vector>

int main() {
    int n;
    std::cin >> n;
    std::vector<int> v;
    for (int i = 0; i < n; ++i) {
        int x;
        std::cin >> x;
        v.push_back(x);
    }
    // print v from the last element to the first
    for (std::size_t i = 0; i < v.size(); ++i) {
        std::cout << v[i];
        if (i + 1 < v.size()) std::cout << " ";
    }
    std::cout << "\n";
    return 0;
}
Input the program receives: 4 ↵ 3 8 1 6
Expected output: 6 1 8 3

Practise this

Exercises for this lesson are in the C++ practice set.

Open the C++ playground

Frequently asked questions

When should I use std::vector instead of a built-in array in C++?

Almost always. A vector knows its size, grows on demand, can be returned from a function and passed by reference without a separate length, and is checked by at(). A built-in array is only preferable for a small fixed-size buffer whose length is a compile-time constant, and even then std::array is safer.

How do I remove an element from a vector by value?

Find its position and erase it: v.erase(std::find(v.begin(), v.end(), value)), after checking that find did not return end(). To remove every occurrence, use std::remove followed by erase, which the STL algorithms lesson explains; C++20 adds std::erase(v, value) that does both steps.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with GCC 13 (C++17) at build time by the publishing checks, and the output shown is what it printed. Running C++ inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C++17) locally.