Vectors (std::vector)

Reviewed & published by Brayan K

std::vector is the container you'll reach for most in C++: a resizable array that grows on demand, knows its own size, and plugs into the entire standard-algorithm library. This lesson takes you from creating and looping over a vector to sorting, summing, counting, and even building a 2D grid.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A std::vector is an expandable shelf. You start with a few slots; when you run out, the shelf quietly buys a bigger unit, moves all your books across, and carries on — you never have to measure and order the shelf yourself. A raw array is a fixed bookcase: pick a size once, and if you guess too small you're stuck. The catch with the expandable shelf is that when it moves to a bigger unit, every book changes location — so a sticky note pointing at "third shelf, second slot" (a reference or iterator) may no longer point where you expect after a big growth.

1. Creating & Accessing

Create a vector with brace initialization and read elements by index from 0. .front() and .back() grab the ends, .size() reports how many elements there are, and .at(i) is the bounds-checked alternative to [i].

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // A vector is a resizable array. Brace-init gives starting values.
    vector<int> scores = {90, 85, 70};

    cout << "size:  " << scores.size() << '\n';   // size:  3
    cout << "first: " << scores.front() << '\n';  // first: 90
    cout << "last:  " << scores.back() << '\n';   // last:  70
    cout << "[1]:   " << scores[1] << '\n';       // [1]:   85

    // .at(i) is bounds-checked (throws on a bad index).
    cout << "at(2): " << scores.at(2) << '\n';    // at(2): 70
    return 0;
}

// ✅ Expected output:
//    size:  3
//    first: 90
//    last:  70
//    [1]:   85
//    at(2): 70

2. Growing & Shrinking

Start with an empty vector and append with push_back; the vector handles all the memory management for you. pop_back removes the last element, and .empty() tells you whether there's anything left. This dynamic resizing is exactly what raw arrays can't do.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<string> tasks;          // starts empty
    cout << "empty? " << tasks.empty() << '\n'; // empty? 1

    // push_back appends to the end and grows as needed.
    tasks.push_back("write code");
    tasks.push_back("test it");
    tasks.push_back("ship it");

    cout << "size: " << tasks.size() << '\n';    // size: 3

    // pop_back removes the last element.
    tasks.pop_back();
    cout << "after pop: " << tasks.size() << '\n'; // after pop: 2
    cout << "now last:  " << tasks.back() << '\n'; // now last:  test it
    return 0;
}

// ✅ Expected output:
//    empty? 1
//    size: 3
//    after pop: 2
//    now last:  test it

3. Three Ways to Loop

An index loop gives you the position (useful when you need i). A range-based loop with const auto& reads each element efficiently without copying. A range-based loop with auto& lets you modify elements in place. Reach for the simplest form that does the job.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> nums = {4, 8, 15, 16, 23, 42};

    // 1) Index loop (you control the index).
    long long sum = 0;
    for (size_t i = 0; i < nums.size(); ++i) sum += nums[i];
    cout << "sum: " << sum << '\n';   // sum: 108

    // 2) Range-based read loop (const auto& avoids copies).
    cout << "items:";
    for (const auto& n : nums) cout << ' ' << n;
    cout << '\n';                     // items: 4 8 15 16 23 42

    // 3) Range-based write loop (auto& modifies in place).
    for (auto& n : nums) n *= 2;
    cout << "doubled[0]: " << nums[0] << '\n'; // doubled[0]: 8
    return 0;
}

// ✅ Expected output:
//    sum: 108
//    items: 4 8 15 16 23 42
//    doubled[0]: 8

4. Vectors + Algorithms

The real power of vectors is how well they pair with the standard algorithms. sort orders them, accumulate (from <numeric>) sums them, and count_if with a lambda counts matches. Learning to combine a container with these algorithms is the heart of effective C++.

#include <iostream>
#include <vector>
#include <algorithm>  // sort, max_element, count_if
#include <numeric>    // accumulate
using namespace std;

int main() {
    vector<int> v = {5, 2, 9, 1, 7};

    sort(v.begin(), v.end());                 // ascending
    cout << "min: " << v.front() << ", max: " << v.back() << '\n';
    // min: 1, max: 9

    int total = accumulate(v.begin(), v.end(), 0); // sum with start 0
    cout << "sum: " << total << '\n';         // sum: 24

    int big = count_if(v.begin(), v.end(),
                       [](int x){ return x > 4; });
    cout << "count > 4: " << big << '\n';     // count > 4: 3
    return 0;
}

// ✅ Expected output:
//    min: 1, max: 9
//    sum: 24
//    count > 4: 3

5. 2D Grids: Vector of Vectors

A vector<vector<int>> models a grid or matrix. The constructor vector<vector<int>>(rows, vector<int>(cols, 0)) builds a rows × cols grid filled with zeros. Index it with two subscripts, grid[r][c], and loop with nested range-based loops.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // A 2D grid: a vector of vectors. (3 rows x 3 cols, all 0.)
    vector<vector<int>> grid(3, vector<int>(3, 0));

    grid[0][0] = 1;
    grid[1][1] = 5;
    grid[2][2] = 9;

    for (const auto& row : grid) {
        for (int cell : row) cout << cell << ' ';
        cout << '\n';
    }
    // 1 0 0
    // 0 5 0
    // 0 0 9
    return 0;
}

// ✅ Expected output:
//    1 0 0
//    0 5 0
//    0 0 9

Your turn. Build a small list and report on it:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 🎯 YOUR TURN — build a list and report on it.
    vector<int> ages;

    // 1) Add three ages with push_back
    ___;            // 👉 ages.push_back(20);
    ages.push_back(35);
    ages.push_back(50);

    // 2) Print how many there are
    cout << "count: " << ___ << '\n';   // 👉 ages.size()

    // 3) Print the last one added
    cout << "last:  " << ___ << '\n';   // 👉 ages.back()

    // ✅ Expected:
    //    count: 3
    //    last:  50
    return 0;
}

🧩 Reorder Challenge

These lines build a vector, sort it, and print the largest value. Reorder so the program prints 9.

A.     vector<int> v = {3, 9, 1};
B. #include <algorithm>
C.     sort(v.begin(), v.end());
D.     cout << v.back() << '\n';
E. int main() {
F.     return 0;
G. }
B. #include <algorithm>
E. int main() {
A.     vector<int> v = {3, 9, 1};
C.     sort(v.begin(), v.end());
D.     cout << v.back() << '\n';
F.     return 0;
G. }

Why: #include <algorithm> must precede the use of sort. Inside main, you build the vector first, then sort it ascending so the largest value lands at the end — which v.back() reads as 9. Return and brace close the function.

🧠 Quick Recall

Predict the output before revealing each answer.

vector<int> v = {1, 2, 3}; v.push_back(4); cout << v.size();

4 — three initial elements plus one push_back makes four.

vector<int> v = {5, 1, 4}; sort(v.begin(), v.end()); cout << v[0];

1 — after an ascending sort the smallest element is at index 0.

vector<int> v = {2, 4}; for (auto& n : v) n += 1; cout << v[0] << v[1];

35 — auto& modifies each element in place, so the vector becomes {3, 5}.

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

OperationDescriptionNote
vector<int> v = {1, 2, 3};create with valuesbrace initialization
v.push_back(x)append to the endgrows automatically (amortized O(1))
v.pop_back()remove the last elementO(1)
v.size()number of elementsunsigned (size_t)
v.empty()true if size is 0clearer than size() == 0
v[i]element at index (no check)fast; UB if out of range
v.at(i)bounds-checked accessthrows std::out_of_range
v.front() / v.back()first / last elementundefined if empty
v.clear()remove all elementssize becomes 0
v.insert(v.begin()+i, x)insert before index iO(n); shifts later elements
v.erase(v.begin()+i)remove element at iO(n); shifts later elements
v.reserve(n)pre-allocate capacityavoids repeated reallocations
for (const auto& x : v)range-for read loopuse auto& to modify in place
v.begin() / v.end()iterators for algorithmssort(v.begin(), v.end())
accumulate(v.begin(), v.end(), 0)sum the elementsfrom <numeric>

Mini-Challenge: Grade Statistics

Compute the high, low, average, and pass count for a vector of grades using the algorithm library. Match the expected output in the comments.

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    // 🎯 MINI-CHALLENGE: grade statistics
    // Given vector<int> grades = {72, 95, 88, 64, 100, 81};
    // 1. Print the highest and lowest grade.
    //    (Hint: sort first, then front()/back(); or use
    //     *max_element / *min_element.)
    // 2. Print the average as a double (cast the sum, divide by size()).
    // 3. Print how many grades are >= 80 with count_if.
    //
    // Expected output (for the data above):
    //    high=100 low=64
    //    average=83.3333
    //    passed=4

    // your code here
    return 0;
}

🎉 Lesson Complete

Practice quiz

After vector<int> v = {1, 2, 3}; v.push_back(4); what is v.size()?

  • 3
  • 5
  • 4
  • 0

Answer: 4. Three initial elements plus one push_back makes four. push_back appends to the end and grows the vector as needed.

What is the difference between size() and capacity()?

  • size() is elements stored; capacity() is how many fit before reallocating
  • They are the same
  • capacity() is always 0
  • size() rounds up to a power of two

Answer: size() is elements stored; capacity() is how many fit before reallocating. size() counts the elements actually stored; capacity() is how many it can hold before it must allocate a bigger block and move everything.

What does push_back past the current capacity typically do to capacity?

  • Adds exactly one slot
  • Leaves it unchanged
  • Halves it
  • Roughly doubles it (geometric growth)

Answer: Roughly doubles it (geometric growth). Vectors grow geometrically (commonly doubling), which keeps repeated push_back amortized O(1).

What does v[i] do for an out-of-range index i?

  • Throws std::out_of_range
  • Is undefined behavior (no bounds check)
  • Returns 0
  • Resizes the vector

Answer: Is undefined behavior (no bounds check). operator[] does no bounds checking, so an out-of-range index is undefined behavior. Use at() for a checked access.

What does v.at(i) do that v[i] does not?

  • Bounds-checks and throws std::out_of_range on a bad index
  • Runs faster
  • Sorts the vector
  • Returns a copy

Answer: Bounds-checks and throws std::out_of_range on a bad index. at() checks the index and throws std::out_of_range if it is out of range, while [] does no check.

Why can a pointer or iterator into a vector suddenly become invalid?

  • The vector is const
  • size() was called
  • A reallocation (growth past capacity) moves all elements to new memory
  • It never becomes invalid

Answer: A reallocation (growth past capacity) moves all elements to new memory. When the vector grows past capacity it reallocates, moving elements to a new block, so old pointers, references, and iterators dangle.

After vector<int> e = {2, 4}; for (auto& n : e) n += 1; what does e[0]e[1] print?

  • 24
  • 35
  • 33
  • 46

Answer: 35. auto& binds a reference to each element, so n += 1 modifies in place: the vector becomes {3, 5}, printing 35.

Which is the efficient way to remove the last element of a vector?

  • erase(v.begin())
  • clear()
  • erase(v.end())
  • pop_back() — O(1)

Answer: pop_back() — O(1). pop_back removes the last element in O(1). Erasing from the middle is O(n) because later elements shift down.

What does sort(v.begin(), v.end()) need passed to it?

  • The size
  • A pair of iterators marking the range
  • An index
  • A capacity hint

Answer: A pair of iterators marking the range. Standard algorithms like sort operate on iterator ranges: begin() and end() mark the half-open range to process.

Erasing a single element from the MIDDLE of a vector with erase(it) is what complexity?

  • O(1)
  • O(log n)
  • O(n) because later elements shift down
  • O(n^2)

Answer: O(n) because later elements shift down. erase from the middle is O(n): every element after the removed one shifts down by one position.

Continue this course

Frequently asked questions

What is the difference between a raw array and a std::vector?

A C-style array has a fixed size set at compile time and does not know its own length. A std::vector grows and shrinks at runtime, tracks its size with .size(), manages its own memory, and works with all the standard algorithms. In modern C++ you should default to std::vector unless you have a specific reason not to.

What is the difference between size() and capacity()?

size() is how many elements are actually stored. capacity() is how many it can hold before it must allocate a bigger block and move everything. When you push_back past the capacity, the vector typically doubles its capacity, which keeps repeated push_back fast on average (amortized O(1)).

Why might a pointer or reference into a vector suddenly become invalid?

When a vector reallocates (because it grew past its capacity), all its elements move to a new memory block, so any pointers, references, or iterators into the old block dangle. Avoid holding references across operations that may grow the vector, or reserve enough capacity up front.

Should I use [] or at() to access elements?

operator[] is fast but does no bounds checking — an out-of-range index is undefined behavior. at() checks the index and throws std::out_of_range on a bad access. Use [] in tight, known-good loops and at() when the index could be wrong, especially with user input.

How do I remove elements efficiently?

To drop the last element, use pop_back (O(1)). To remove elements by value, use the erase-remove idiom: v.erase(std::remove(v.begin(), v.end(), value), v.end()). Erasing a single element from the middle with erase(it) is O(n) because everything after it shifts down.

Related lessons