A C++ array is a fixed-size block of same-typed elements stored next to each other in memory. You declare it as int a[5];, index it from 0, and the compiler does not check your index. For most new code, std::vector (size known at run time) or std::array (size known at compile time) is the better choice; plain C-style arrays still matter because you will read them in existing code and interviews ask about how they behave.
Declaring and initializing
The size must be a compile-time constant. An array declared inside a function and not initialized contains garbage, so reading it is a bug.
int a[5] = {1, 2, 3}; // 1 2 3 0 0 - missing elements become 0
int b[] = {5, 2, 8, 1, 9}; // size 5 is deduced
int z[4] = {}; // all zeros
int local[3]; // uninitialized: indeterminate values
If the braces hold fewer values than the size, the rest are zero. If they hold more, the program does not compile. Brace initialization without = (int n[5]{1, 2, 3, 4, 5};) is equivalent. Variable-length arrays (int a[n]; with a runtime n) are a C feature and a common compiler extension, but they are not standard C++; use std::vector.
Accessing elements and looping
int numbers[5] = {1, 2, 3, 4, 5};
cout << numbers[0]; // 1
numbers[2] = 10;
for (int x : numbers) cout << x << ' '; // range-for works on a real array
Valid indexes are 0 to size – 1. numbers[5] or numbers[-1] compiles and has undefined behavior: it may print garbage, crash, or appear to work and corrupt other data. Nothing in the language warns you. std::vector::at(i) is the checked alternative, and it throws std::out_of_range.
To find the element count of a real array in the same scope, use sizeof(b) / sizeof(b[0]) (20 / 4 = 5 for the array above) or, in C++17, std::size(b). This brings us to the biggest trap.
Array decay: why sizeof fails inside a function
When you pass an array to a function, it “decays” into a pointer to its first element. The parameter int arr[] is really int* arr, so the function has no idea how long the array is.
int sumArr(const int arr[], size_t n) {
cout << sizeof(arr); // size of a pointer, not of the array
int s = 0;
for (size_t i = 0; i < n; i++) s += arr[i];
return s;
}
int b[] = {5, 2, 8, 1, 9};
cout << sumArr(b, 5); // 25; sizeof(arr) printed 8 on a 64-bit system
Inside sumArr, sizeof(arr) printed 8 (the size of a pointer on a 64-bit system), while sizeof(b) in the caller is 20. The compiler even warns about it. So always pass the length with the pointer, or pass a reference to a sized array (const int (&arr)[5]), or use std::vector, std::array or (C++20) std::span.
Two more consequences of decay and pointers: arrays do not copy on assignment (int* q = p; makes q point at the same data, so q[0] = 99 changes p[0]), and == on two arrays compares their addresses, not their contents. Two arrays with the same values compared with == gave 0; std::equal(c, c + 3, e) gave 1. A vector copies on assignment and compares by contents.
Two-dimensional arrays
A 2D array is an array of arrays, stored row by row in one contiguous block:
int m[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
cout << m[1][2]; // 7: row 1, column 2
int* flat = &m[0][0];
cout << flat[1 * 4 + 2]; // 7 again: element (row, col) sits at row * cols + col
When you pass one to a function, every dimension except the first must be part of the parameter type (int m[][4]). That is rigid, so interview code usually uses vector<vector<int>>:
vector<vector<int>> grid(3, vector<int>(4, 0)); // 3 rows, 4 columns, all 0
grid[1][2] = 7;
Iterate rows in the outer loop and columns in the inner loop; that matches the memory layout of the C-style array and is faster for large grids.
Choosing: C array, new[], vector or std::array
| Type | Size known | Knows its size | Copies on assignment | Frees itself |
|---|---|---|---|---|
int a[5] |
compile time | no (decays) | no | yes (automatic storage) |
new int[n] |
run time | no | no (pointer copy) | no, needs delete[] |
std::array<int, 5> |
compile time | yes | yes | yes |
std::vector<int> |
run time, can grow | yes | yes | yes |
A manual dynamic array looks like this, and every new[] must be matched by exactly one delete[] (not plain delete):
int n = 5;
int* d = new int[n](); // the () zero-initializes
d[2] = 4;
delete[] d;
Forgetting delete[] leaks memory, and using the pointer afterwards is undefined behavior. A vector does this for you, which is why the usual advice is to use it unless you have a specific reason not to. You still pay for one thing: push_back occasionally reallocates, which invalidates pointers and iterators into the vector.
vector<int> v = {1, 2, 3, 4, 5};
v.push_back(6); // size() is now 6
v.at(10); // throws std::out_of_range
array<int, 5> st = {1, 2, 3, 4, 5}; // size() == 5, copies like a value
One size() trap applies to both: it returns an unsigned type, so on an empty vector v.size() - 1 wraps around to 18446744073709551615 on a 64-bit system. Write i + 1 < v.size() instead of i < v.size() - 1, or cast to int.
To practice these basics in small steps, AlgoCademy’s C++ course has lessons on static arrays, vectors and exceeding array bounds.
Standard algorithms that work on arrays
Pointers into an array are valid iterators, so the standard algorithms work directly on C arrays. Include <algorithm>:
int b[] = {5, 2, 8, 1, 9};
sort(b, b + 5); // 1 2 5 8 9
bool has8 = binary_search(b, b + 5, 8); // true; needs sorted input
auto it = find(b, b + 5, 8); // it - b == 3
auto lb = lower_bound(b, b + 5, 5); // first element >= 5; lb - b == 2
reverse(b, b + 5); // 9 8 5 2 1
With a vector, use v.begin() and v.end() in place of b and b + 5. Prefer these over hand-written bubble sort or linear search outside of learning exercises: sort runs in O(n log n), bubble sort in O(n^2). Writing binary search yourself is still a common interview task. Compute the midpoint as left + (right - left) / 2 to avoid overflow, and note that it requires sorted input (there is a Binary Search lesson for it).
Array problems that come up in interviews
These three cover most beginner questions. All were compiled and run on the examples shown.
Two Sum
Find two indexes whose values add to a target. A hash map from value to index gives O(n) time:
vector<int> twoSum(const vector<int>& nums, int target) {
unordered_map<int, int> seen;
for (int i = 0; i < (int)nums.size(); i++) {
auto it = seen.find(target - nums[i]);
if (it != seen.end()) return {it->second, i};
seen[nums[i]] = i;
}
return {};
}
// twoSum({2, 7, 11, 15}, 9) returns {0, 1}
Rotate an array right by k
Three reversals do it in place in O(n) time and O(1) extra space. Reduce k first, and note that an empty array would divide by zero in k %= size.
void rotateRight(vector<int>& nums, int k) {
k %= (int)nums.size();
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
// {1,2,3,4,5,6,7} with k = 3 becomes {5,6,7,1,2,3,4}
Maximum subarray (Kadane’s algorithm)
At each element, either extend the current run or start a new one:
int maxSubArray(const vector<int>& nums) {
int best = nums[0], cur = nums[0];
for (size_t i = 1; i < nums.size(); i++) {
cur = max(nums[i], cur + nums[i]);
best = max(best, cur);
}
return best;
}
// {-2,1,-3,4,-1,2,1,-5,4} returns 6 (the subarray 4, -1, 2, 1)
There is a longer walkthrough in Kadane’s algorithm, and strategies for array manipulation problems covers the wider set of patterns.
Checklist of common mistakes
- Reading or writing index
size(off by one). Loop withi < size, not<=. - Using an uninitialized local array.
- Taking
sizeofof a function parameter and expecting the element count. - Comparing arrays with
==, or expecting assignment to copy them. - Mixing
new[]withdelete, or forgetting to free. - Holding a pointer or reference into a vector across a
push_back. - Subtracting from
size()on a possibly empty container. - Allocating a very large array as a local variable: the stack is small (often a few megabytes), so put big arrays in a
vector, or make themstaticor global.
