When working with arrays, one of the most common tasks is moving through and visiting each element in an array one by one to read, manipulate (modify), or process the elements and their data. This process is called array traversal, and in this article, we'll explore how it works with two-dimensional arrays. Working with 2D arrays can feel abstract at first, but once visualized as grids or tables, common patterns become much clearer. Arrays are among the most fundamental data structures in programming, and understanding how to work with them efficiently is crucial for solving real-world problems. While simple one-dimensional arrays store a single list of items (like a row of vegetables), 2D arrays add another layer of organization. They store arrays within arrays, creating a grid-like structure. Think of the difference between a single row of carrots versus an entire garden plot with multiple rows and columns of different vegetables.
To make this clearer, it's important to understand the distinction between two-dimensional arrays and multidimensional arrays:
Two-Dimensional: These are arrays with exactly two dimensions, which are rows and columns. A 2D array is like a flat garden plot or a spreadsheet. Each element can be accessed using two indices: array[row][column]. Most common use cases involve 2D arrays: game boards (chess, tic-tac-toe), image pixel data, spreadsheet-like data, and seating charts.
Multidimensional Arrays: These extend beyond two dimensions, such as a 3D array, which is like stacking multiple garden plots on top of each other and requires three (or more) indices to access elements: array[layer][row][column]. Examples include 3D graphics and modeling, video data (frames Γ height Γ width), and scientific simulations with multiple variables across space and time.
This article focuses specifically on 2D arrays because they represent the sweet spot of complexity. They are complex enough to require special traversal patterns, but simple enough to visualize and understand intuitively. Mastering 2D array patterns provides the foundation for working with higher-dimensional arrays when needed.
Recognizing common 2D array patterns allows developers to:
- Write cleaner code: Using established patterns makes code more readable and maintainable.
- Solve problems faster: Many algorithmic challenges involve 2D grids and matrices.
- Optimize performance: Knowing the right traversal pattern can improve efficiency.
- Debug more effectively: Understanding how data is accessed makes it easier to spot issues.
Makes sense? And I am sure it is fairly straightforward. So now, let's define a 2D array (and go deeper). As previously mentioned, think of it like a vegetable garden plot divided into rows and beds. To access any vegetable, two indices are used: garden[row][column]. For example, garden[1][2] gives the bell pepper π« (row 1, column 2).
const garden = [
['π₯', 'π₯¬', 'π½'],
['π
', 'π₯', 'π«'],
['π₯', 'π§
', 'π₯¦']
];
It's simply an array of arrays! And in this article, we will cover three essential 2D array traversal patterns:
Main Diagonal Traversal: Moving from the top-left corner to the bottom-right corner, following a diagonal path through the array. This pattern is essential for algorithms that involve diagonal relationships, such as checking winning conditions in board games or processing the diagonal elements of a matrix.
Anti-Diagonal Traversal: Crossing from the top-right corner to the bottom-left corner. This complementary pattern is useful for checking the opposite diagonal, completing board game logic, or processing data that flows in the reverse diagonal direction.
Border Traversal: Walking around the perimeter of the array, visiting all edge elements while skipping internal ones. This pattern appears frequently in problems involving boundaries, such as image edge detection, flood fill algorithms, and spiral matrix traversals.
Pattern 1: Main Diagonal Traversal
Imagine following a diagonal garden path from the top-left corner (carrots) to the bottom-right corner (broccoli). With each step, the movement is down one row AND right one column at the same time.
Step 0: Row 0, Col 0 β ['π₯', 'π₯¬', 'π½']
β
Step 1: Row 1, Col 1 β ['π
', 'π₯', 'π«']
β
Step 2: Row 2, Col 2 β ['π₯', 'π§
', 'π₯¦']
Since both the row and the column increase by the same amount, they're always equal. When traversing the main diagonal, the row index and column index are always the same:
// access: π₯, π₯, π₯¦
for (let i = 0; i < garden.length; i++) {
console.log(garden[i][i]);
}
It works because at each step i, the access is garden[i][i]:
- Step 0:
garden[0][0]= π₯ (carrot) - Step 1:
garden[1][1]= π₯ (cucumber) - Step 2:
garden[2][2]= π₯¦ (broccoli)
Pattern 2: Anti-Diagonal Traversal
Now imagine sliding from the top-right corner to the bottom-left corner. Each step, you move down one row, BUT left one column; they move in opposite directions.
Step 0: Row 0, Col 2 β [1, 2, 3]
β
Step 1: Row 1, Col 1 β [4, 5, 6]
β
Step 2: Row 2, Col 0 β [7, 8, 9]
Notice the pattern as the row increases, the column decreases:
- Row goes: 0 β 1 β 2 (increasing)
- Column goes: 2 β 1 β 0 (decreasing)
let size = matrix.length; // same as garden.length
// access: 3, 5, 7
for (let i = 0; i < size; i++) {
console.log(matrix[i][(size-1)-i]);
}
And this works because the formula ((size-1)-i)) gives the decreasing column index:
- Step 0:
matrix[0][2]= 3 (where 2 = (3-1)-0) - Step 1:
matrix[1][1]= 5 (where 1 = (3-1)-1) - Step 2:
matrix[2][0]= 7 (where 0 = (3-1)-2)
Pattern 3: Border Traversal
The trickier one, until it's understood, is just combining both diagonals. Imagine walking around the perimeter of a garden, following the fence. This path visits every vegetable on the outer edge, but skips everything in the middle beds.
Using this larger garden as the example:
START β β β β
β ['π₯' 'πΏ' 'πΎ' 'π½'] β
β ['π
' 'π₯' 'π«' 'πΆοΈ'] β
β ['π₯' 'π§
' 'π§' 'π₯¦'] β
β β β β END
The border vegetables are: π₯, πΏ, πΎ, π½, πΆοΈ, π₯¦, π§, π§
, π₯, π
The inside vegetables (NOT border): π₯, π«
The key to border traversal is understanding that the path follows four separate fence sides, and on each side, one coordinate stays fixed while the other changes.
Side 1: North Fence (Moving East)
The path is on row 0, walking across all columns:
// row = 0 (fixed), col = 0, 1, 2, 3 (changing)
// gets: π₯, πΏ, πΎ, π½
for (let col = 0; col < 4; col++) {
console.log(garden[0][col]);
}
Side 2: East Fence (Moving South)
The path is in the last column, walking down all rows:
// col = 3 (fixed), row = 0, 1, 2 (changing)
// gets: π½, πΆοΈ, π₯¦
for (let row = 0; row < 3; row++) {
console.log(garden[row][3]);
}
Side 3: South Fence (Moving West)
The path is on the last row, walking backwards across columns:
// row = 2 (fixed), col = 3, 2, 1, 0 (changing backwards)
// gets: π₯¦, π§, π§
, π₯
for (let col = 3; col >= 0; col--) {
console.log(garden[2][col]);
}
Side 4: West Fence (Moving North)
The path is on column 0, walking up the rows:
// col = 0 (fixed), row = 2, 1, 0 (changing backwards)
// gets: π₯, π
, π₯
for (let row = 2; row >= 0; row--) {
console.log(garden[row][0]);
}
Now, one might ask? How to find these magic numbers that make up the column and rows? And how does this all look together? Combining all four fence sides:
function traverseGardenBorder(garden)
{
const rows = garden.length; // magic number
const cols = garden[0].length; // magic number
const result = [];
// north fence (west to east)
for (let col = 0; col < cols; col++) {
result.push(garden[0][col]);
}
// east fence (north to south, skip top corner)
for (let row = 1; row < rows; row++) {
result.push(garden[row][cols - 1]);
}
// south fence (east to west, skip east corner if more than 1 row)
if (rows > 1) {
for (let col = cols - 2; col >= 0; col--) {
result.push(garden[rows - 1][col]);
}
}
// west fence (south to north, skip both corners if more than 1 column)
if (cols > 1) {
for (let row = rows - 2; row >= 1; row--) {
result.push(garden[row][0]);
}
}
return result;
}
Once you understand how a 2D array is structured and picture it as movement through a space (following paths, crossing plots, and walking fences), traversal patterns become much more intuitive. The most common approach is row-wise traversal, where you visit elements left to right and top to bottom, similar to reading a page. Column-wise traversal shifts the direction, moving top to bottom through each column instead.
After the basics, you can explore more interesting patterns. Reverse traversal lets you work backward from the end. Diagonal traversal helps reveal connections across rows and columns. Spiral traversal moves around the edges and works inward, layer by layer, which often shows up in coding challenges and practical data-processing tasks.
And to close this article, if anyone is wondering why array traversal is such a fundamental concept, it is because it appears in:
- Searching Elements
- Sorting Algorithms
- Summing or Averaging Values
- Filtering Data
- Mapping one array to another βοΈ
- And, almost every algorithm that works with arrays!
#HappyCoding
Top comments (0)