Arrays Vectors
By the end of this lesson you'll be able to store a whole list of values in one variable, read and change any item by its index, and grow a collection on demand with std::vector < int > — the container you'll reach for in almost every C++ program you write.
Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
A C-style array is a fixed row of post-office boxes . You decide how many boxes to build, and that number can never change. Each box has a number on it starting at 0 , and you reach a box by its number — scores[0] , scores[1] , and so on. There's no security guard: if you ask for box number 5 on a 5-box wall (boxes 0–4), nobody stops you — you just grab whatever is in the next bit of wall, which is a bug.
A std::vector is the same wall of boxes, but magic and expandable : you can add a new box on the end any time with push_back , it always knows how many boxes it has with size() , and its .at() door has a guard that refuses an invalid box number. That's why modern C++ reaches for vector first.
1. C-Style Arrays
An array stores many values of one type in a single named block. You declare it as type name[count] , and you reach each value by its index — an offset that starts at 0 . So int a[5] holds five ints at indexes 0, 1, 2, 3, and 4; the last valid index is always count - 1 . Two things to burn in now: the size is fixed (you can't grow it), and there is no bounds checking (reading past the end is undefined behaviour, not an error). Read this worked example, run it, then you'll write your own.
Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints, then run it. Watch the indexes carefully.
2. std::array — a Safer Fixed Array
A C-style array forgets its own length, so you end up passing a separate size around by hand. std::array < type, count > fixes that: it's still fixed-size and lives on the stack like a raw array, but it knows its size() and offers a bounds-checked .at() . You need #include < array > to use it. Reach for it when the count is known up front and never changes.
3. std::vector — the One You'll Use Most
A vector is a resizable array. You add to the end with push_back , ask how big it is with size() , and read items with [] (fast, unchecked) or .at() (checked — it throws if the index is invalid). front() and back() grab the first and last item, and a range-based for visits every element without you managing an index. You need #include < vector > . For almost every list of data, this is the container to use.
Now you build one. Start from an empty vector and add items to it, then read it back. Fill in the blanks:
4. 2D Vectors (a Grid)
Sometimes data is a grid — a board, a spreadsheet, an image. A vector < vector < int > > is a vector whose elements are themselves vectors, so you index it with two brackets: grid[row][col] . The outer size() gives the number of rows, and grid[row].size() gives the columns in that row. A nested for visits every cell.
🔎 Deep Dive: [] vs .at()
v[i] trusts you completely — it does no bounds checking . If i is out of range you get undefined behaviour : maybe a crash, maybe garbage, maybe a silent corruption that bites you ten lines later. It's the fast path for indexes you've already validated (like a clean for loop).
v.at(i) does the same read but checks the index first and throws a std::out_of_range exception if it's invalid. Use it whenever the index could be wrong — for example, an index that came from user input. A loud, immediate error beats a mysterious one.
Common Errors (and the fix)
- Out-of-bounds access (undefined behaviour): reading a[5] on a size-5 array touches memory you don't own. There's no error message — it just misbehaves. Stay within 0 to size - 1 , and use .at() when an index might be wrong.
- Off-by-one in loops: for (int i = 0; i <= 5; i++) on a size-5 array runs one step too far and touches a[5] . Use i < size (strictly less-than), not <= .
- Modifying a vector while iterating it: calling push_back or erase inside a range-based for can reallocate the storage and invalidate your loop. Finish the loop first, then change the vector.
- [] hides mistakes that .at() would catch: v[i] never tells you the index was bad. If a value looks like garbage, swap to v.at(i) temporarily — it'll throw exactly where the bad index is.
- "error: 'vector' was not declared in this scope": you forgot #include < vector > (or < array > for std::array ). Add the header at the top.
📋 Quick Reference
Task
Code
Notes
Declare array
Fixed size, no bounds check
Declare std::array
#include <array>
Declare vector
#include <vector>
Add to end
v.push_back(4);
Vector only
Count
v.size()
Current element count
Read (fast)
v[i]
No bounds check
Read (safe)
v.at(i)
Throws if out of range
First / last
v.front() / v.back()
No index maths
Iterate
Range-based for
Frequently Asked Questions
Mini-Challenge: Top Score & Total
No blanks this time — just a brief and an outline to keep you on track. Build it, run it, and check your output against the example in the comments. Finding the max and the sum of a list is a pattern you'll use constantly.
🎉 Lesson Complete
- ✅ A C-style array int a[5] is fixed-size, 0-indexed, with no bounds checking
- ✅ The last valid index is size - 1 ; i < size in loops avoids off-by-one
- ✅ std::array < type, count > is a safer fixed array that knows its size()
- ✅ std::vector grows with push_back and reports its size()
- ✅ [] is fast but unchecked; .at() checks bounds and throws
- ✅ Use front / back , range-for, and vector < vector < int > > for grids — and prefer vector by default
- ✅ Next lesson: Pointers & References — unlock direct access to memory
Practice quiz
What is the index of the FIRST element in a C-style array?
- 1
- 0
- -1
- It depends on the type
Answer: 0. Array indexing starts at 0; the first element sits 0 steps from the start.
For an array of size 5, what is the last valid index?
- 5
- 6
- 4
- 0
Answer: 4. Valid indexes are 0..size-1, so the last is 4 for a size-5 array.
What is true about a C-style array's size?
- It grows automatically
- It is fixed and cannot grow
- It is always 10
- It changes with push_back
Answer: It is fixed and cannot grow. C-style arrays are fixed-size; you cannot push_back or grow them.
Which container is the resizable, grow-on-demand array of modern C++?
- std::array
- C-style array
- std::vector
- std::list only
Answer: std::vector. std::vector resizes on demand and is the go-to container in modern C++.
Which method adds an element to the end of a std::vector?
- append()
- push_back()
- add()
- insert_end()
Answer: push_back(). push_back() adds an element to the end and grows the vector.
What is the difference between v[i] and v.at(i)?
- They are identical
v.at(i) checks bounds and throws std::out_of_range; v[i] does no checking.
Which header must you include to use std::vector?
- <array>
- <vector>
- <list>
- <collection>
Answer: <vector>. std::vector requires #include <vector>.
How do you index a 2D vector named grid at row 1, column 2?
- grid(1,2)
A vector of vectors is indexed with two brackets: grid[row][col].
What does reading a[5] on a size-5 array cause?
- A compile error
- Undefined behavior
- It returns 0
- It throws std::out_of_range
Answer: Undefined behavior. Index 5 is out of range on a size-5 array; C-style [] does no bounds checking, so it is undefined behavior.
What does grid.size() return for a 2D vector?
- The total number of cells
- The number of rows
- The number of columns
- Always 1
Answer: The number of rows. The outer size() gives the number of rows; grid[r].size() gives that row's columns.
Continue this course
- Previous: Functions
- Next: Pointers & References