Menu

C++ vector: Dynamic Arrays with std::vector Explained

std::vector is C++'s resizable array - the container you should reach for by default. Learn how to create, access, grow, and loop over a vector, plus the iterator-invalidation and out-of-bounds gotchas.

This page includes runnable editors - edit, run, and see output instantly.

Why vector Instead of a Raw Array

A raw array has a fixed size baked in at compile time and forgets how long it is the moment you pass it to a function. std::vector fixes both problems: it's a resizable array that tracks its own length, grows on demand, and cleans up its own memory. In modern C++, vector is the default container - reach for a raw array only when you have a specific reason not to.

Include <vector>, then declare one with the element type in angle brackets:

scores.size() always tells you the current length - no separate int n to keep in sync, and no sizeof tricks. The {90, 75, 100, 60} is a brace initializer; the vector figures out it needs four slots.

Creating and Initializing a vector

There are several ways to build one, depending on what you know up front:

Watch the parentheses-vs-braces trap: vector<int> tens(5, 10) makes five copies of 10, while vector<int> tens{5, 10} makes a two-element vector holding 5 and 10. Parentheses mean "size and fill value"; braces mean "these literal elements."

Adding and Removing Elements

The whole point of a vector is that it grows. push_back appends to the end, and pop_back removes from the end:

back() returns the last element and front() the first - cleaner than v[v.size() - 1] and v[0]. Since C++11 you can also use emplace_back(args...) to construct an element in place, which avoids a temporary copy for heavier types.

A common beginner mistake is calling front() or back() on an empty vector. That's undefined behavior, not an error - always guard with if (!v.empty()) first.

Reading Elements: [] vs at()

You index a vector exactly like an array with []. But [] does no bounds checking - an out-of-range index is undefined behavior, which can silently read garbage or crash later in a confusing place:

vector<int> v = {1, 2, 3};
cout << v[10];   // UNDEFINED BEHAVIOR - no check, no error

When you want safety, use at(). It checks the index and throws std::out_of_range on a bad access - one of the standard library exceptions you can catch - so you get a clear failure instead of corruption:

Rule of thumb: use [] in tight loops where you've already proven the index is valid, and at() at boundaries where bad input could sneak in.

Looping Over a vector

The cleanest way to walk a vector is the range-based for loop. Take elements by const auto& to read without copying, or auto& to edit them in place:

If you actually need the index (to compare neighbors, say), use a classic counting loop - but note that size() returns an unsigned type (size_t). Comparing a signed int i against it can trigger compiler warnings and surprising wraparound, so prefer size_t i or a range-based loop when you can:

for (size_t i = 0; i < v.size(); i++) {   // size_t, not int
    cout << v[i];
}

size, capacity, and reserve

A vector keeps two numbers: size() (how many elements it holds) and capacity() (how many it can hold before it must grow). When a push_back exceeds capacity, the vector allocates a bigger block, copies every element over, and frees the old block. That's why repeated push_back is amortized cheap but each individual reallocation isn't free:

If you know roughly how many elements you'll add, call reserve() first to skip the repeated reallocations. Note that reserve() changes capacity, not size - the vector still has zero elements until you push them.

This reallocation is also the source of the nastiest vector bug. Because growing moves the storage, any pointer, reference, or iterator you saved into the vector becomes dangling after a push_back that reallocates:

vector<int> v = {1, 2, 3};
int& first = v[0];     // reference into the vector
v.push_back(4);        // may reallocate...
cout << first;         // DANGLING - may point at freed memory

The same applies to iterators: don't push_back or erase while iterating with a saved iterator. If you must remove items while looping, use the return value of erase, or the erase-remove idiom with std::remove.

Next: map

A vector is perfect when you look things up by position - element 0, element 1, and so on. But often you want to look things up by a key instead: a username, a product ID, a word. That's what std::map is for. Next we'll cover map, C++'s key-value container, including how to insert, look up, and iterate entries - and the []-creates-a-default gotcha that trips up almost everyone.

Frequently Asked Questions

What is a vector in C++?

A std::vector is a dynamic (resizable) array from the C++ Standard Library. Unlike a raw array, it knows its own size, grows automatically when you add elements with push_back, and frees its memory for you. Include <vector> and write vector<int> v; to make one.

What is the difference between [] and at() on a C++ vector?

v[i] does no bounds checking - an out-of-range index is undefined behavior (crash or silent corruption). v.at(i) checks the index and throws std::out_of_range if it's invalid. Use [] in hot loops where you've already validated the index, and at() when you want a safe, debuggable failure.

Does push_back invalidate pointers and references into a C++ vector?

Yes, potentially. When a vector runs out of capacity, push_back reallocates its storage to a new block, which invalidates every pointer, reference, and iterator into the old elements. Don't hold a reference to an element across a push_back, and call reserve() up front if you can to avoid surprise reallocations.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED