Question
How can I declare a 2D array dynamically in C++ using new?
For a one-dimensional array, I would write:
int* ary = new int[size];
But this does not work the way I expect:
int** ary = new int[sizeY][sizeX];
It either fails to compile or does not behave like a normal 2D array such as:
int ary[sizeY][sizeX];
What is the correct way to allocate a 2D array with new, and how is it different from a regular stack-allocated 2D array?
Short Answer
By the end of this page, you will understand how dynamic 2D arrays work in C++, why int** is not the same as int[sizeY][sizeX], how to allocate and free memory correctly with new, and when to prefer safer alternatives like std::vector.
Concept
In C++, a true 2D array and a pointer-to-pointer are not the same thing.
A fixed 2D array like this:
int ary[rows][cols];
creates one contiguous block of memory containing rows * cols integers. The compiler knows the column size and can calculate where each element lives.
When you use new, you are doing dynamic allocation, which means memory is created at runtime instead of being placed on the stack.
There are two common ways people try to build a 2D array dynamically:
-
Array of pointers
int** ary = new int*[rows]; for (int i = 0; i < rows; i++) { ary[i] = new int[cols]; }This gives you rows that can be accessed with
ary[i][j], but the rows are allocated separately. -
Single contiguous block
int* data = new int[rows * cols];Then you access elements manually using an index formula:
Mental Model
Think of a fixed 2D array as a spreadsheet printed on a single sheet of paper.
- Every cell is laid out in one continuous grid.
- The compiler knows exactly how wide each row is.
Now think of int** as a folder of separate sheets.
- The folder holds pointers to each row.
- Each row can be allocated separately.
- The rows might not be next to each other in memory.
Both can look similar when you write ary[i][j], but internally they are organized differently.
A contiguous 2D array is one big rectangle. A pointer-to-pointer is a list of smaller rectangles.
Syntax and Examples
1. Dynamic 2D array using an array of pointers
int rows = 3;
int cols = 4;
int** ary = new int*[rows];
for (int i = 0; i < rows; i++) {
ary[i] = new int[cols];
}
ary[1][2] = 42;
To free the memory:
for (int i = 0; i < rows; i++) {
delete[] ary[i];
}
delete[] ary;
This works, but each row is allocated separately.
2. Dynamic 2D array using one contiguous block
int rows = 3;
int cols = 4;
int* ary = new int[rows * cols];
ary[1 * cols + 2] = 42;
To free it:
delete[] ary;
Step by Step Execution
Consider this example using an array of pointers:
int rows = 2;
int cols = 3;
int** ary = new int*[rows];
for (int i = 0; i < rows; i++) {
ary[i] = new int[cols];
}
ary[0][0] = 10;
ary[0][1] = 20;
ary[1][2] = 30;
Step by step:
-
int** ary = new int*[rows];- Allocate space for
rowspointers. arynow points to an array that can hold row addresses.
- Allocate space for
-
ary[i] = new int[cols];- For each row, allocate an array of
colsintegers. - Now each
ary[i]points to one row.
- For each row, allocate an array of
-
ary[0][0] = 10;
Real World Use Cases
Dynamic 2D structures are useful when dimensions are not known until runtime.
Common examples
- Game boards
- Tic-tac-toe, chess, tile maps, pathfinding grids
- Image processing
- Pixels arranged by row and column
- Spreadsheets or tables
- Rows and columns loaded from a file or database
- Scientific computing
- Matrices, heatmaps, simulation grids
- Dynamic input data
- A grid size entered by the user
Example: reading a map size from input
int rows, cols;
std::cin >> rows >> cols;
int** grid = new int*[rows];
for (int i = 0; i < rows; i++) {
grid[i] = new int[cols];
}
This is useful when rows and cols are not compile-time constants.
Real Codebase Usage
In real C++ projects, raw new for 2D arrays is much less common than it used to be.
What developers usually do
Use std::vector
std::vector<std::vector<int>> grid(rows, std::vector<int>(cols, 0));
This is common because:
- memory is cleaned up automatically
- resizing is easier
- code is safer and clearer
Use one flat container for performance
std::vector<int> grid(rows * cols, 0);
auto at = [&](int r, int c) -> int& {
return grid[r * cols + c];
};
This pattern is common in:
- game engines
- numeric code
- image buffers
- performance-sensitive code
Use guard clauses for bounds
if (r < 0 || r >= rows || c < 0 || c >= cols) {
;
}
Common Mistakes
1. Assuming int** is the same as a 2D array
Broken code:
int** ary = new int[sizeY][sizeX];
Why it is wrong:
new int[sizeY][sizeX]does not produce anint**- a pointer-to-pointer and a 2D array type are different
2. Forgetting to allocate each row
Broken code:
int** ary = new int*[rows];
ary[0][0] = 5;
Problem:
ary[0]does not point to a valid row yet
Fix:
for (int i = 0; i < rows; i++) {
ary[i] = new int[cols];
}
3. Forgetting to delete every row
Broken cleanup:
[] ary;
Comparisons
| Approach | Syntax | Memory layout | Pros | Cons |
|---|---|---|---|---|
| Fixed 2D array | int a[r][c] | Contiguous | Simple, fast indexing | Size usually must be known in advance |
| Pointer-to-pointer | int** a with row allocations | Separate rows | Flexible row allocation | More complex cleanup, not truly contiguous |
| Flat dynamic array | int* a = new int[r*c] | Contiguous | Efficient, simple allocation | Manual index math |
std::vector<std::vector<int>> | vector<vector<int>> |
Cheat Sheet
Quick reference
1D dynamic array
int* a = new int[size];
delete[] a;
2D dynamic array with separate rows
int** a = new int*[rows];
for (int i = 0; i < rows; i++) {
a[i] = new int[cols];
}
// use a[i][j]
for (int i = 0; i < rows; i++) {
delete[] a[i];
}
delete[] a;
2D dynamic array as one flat block
int* a = new int[rows * cols];
// element at row r, col c:
a[r * cols + c]
delete[] a;
True dynamic 2D array when column size is known
const int cols = 4;
int (*a)[cols] = new int[rows][cols];
a[1][] = ;
[] a;
FAQ
Why does int** ary = new int[rows][cols]; not compile?
Because new int[rows][cols] does not return an int**. It returns a pointer to an array type, not a pointer to pointer.
Is int** a real 2D array in C++?
Not exactly. It is usually an array of pointers, where each pointer refers to a row allocated separately.
Which dynamic 2D approach is best?
For most code, use std::vector. For performance and contiguous memory, use a flat std::vector<int> or flat dynamic array.
Can I use ary[i][j] with dynamic allocation?
Yes, if you allocate rows separately with int**, or if you use a pointer-to-array type such as int (*ary)[cols].
Do I need to free memory manually when using new?
Yes. Anything allocated with new or new[] must be released with delete or delete[].
Why do many developers avoid raw ?
Mini Project
Description
Build a small grid program that creates a dynamic 2D structure, fills it with values, prints it, and then cleans up memory correctly. This demonstrates how row and column indexing works and reinforces the difference between a pointer-to-pointer and a contiguous block.
Goal
Create and use a runtime-sized numeric grid in C++ and free all allocated memory correctly.
Requirements
Requirement 1 Requirement 2 Requirement 3
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.