2D Array Traversals: Nested Loops
Nested Loops
Since a 2D array is really an array of arrays, traversing every element takes a loop inside a loop — the outer loop walks the rows, the inner loop walks the columns of whichever row the outer loop is currently on:
double[][] moduleCurrents = { {12.1, 11.8, 40.2}, {12.0, 11.9, 12.1} };
for (int row = 0; row < moduleCurrents.length; row++)
{
for (int col = 0; col < moduleCurrents[0].length; col++)
{
System.out.println(moduleCurrents[row][col]);
}
}
The loop body runs rows × columns times total. moduleCurrents.length is the row bound; moduleCurrents[0].length is the column bound (safe here because the array is rectangular — every row has the same length).
Row-Major vs. Column-Major
Which index changes in the outer loop decides the traversal order:
// Row-major: finish one module's samples before moving to the next module
for (int row = 0; row < moduleCurrents.length; row++)
for (int col = 0; col < moduleCurrents[0].length; col++)
process(moduleCurrents[row][col]);
// Column-major: look at sample #0 across ALL modules, then sample #1 across all modules...
for (int col = 0; col < moduleCurrents[0].length; col++)
for (int row = 0; row < moduleCurrents.length; row++)
process(moduleCurrents[row][col]);
Row-major is the natural choice for "how did this one module behave over time." Column-major is the natural choice for "what was total robot current draw at this one instant" — e.g. checking whether all 4 modules spiked simultaneously (a brownout risk), which row-major order would never surface directly.
Enhanced For-Each, Nested
A nested for-each also traverses a 2D array, with no indices — but only when you don't need row/column numbers or in-place modification of primitive values:
for (double[] moduleRow : moduleCurrents) // outer variable is a 1D array — one row
{
for (double sample : moduleRow) // inner variable matches the element type
{
System.out.println(sample);
}
}
Pattern to remember: the outer loop variable is typed as a 1D array (a whole row); the inner loop variable is typed as one element. As with 1D for-each (Lesson 9.4), reassigning sample inside the loop does not change what's stored in the array.
2D Arrays of Objects Are Different
The for-each modification limit above is about primitives. A 2D array of objects — like a grid of TalonFX motor wrappers, or a vision system's Pixel[][] — can still have its objects mutated through a for-each loop, because you're calling a method on the object the loop variable refers to, not reassigning the array slot itself:
for (TalonFX[] moduleRow : driveMotors)
{
for (TalonFX motor : moduleRow)
{
motor.setNeutralMode(NeutralModeValue.Brake); // mutates the real object — this works
}
}
Common Pitfalls
- Using
moduleCurrents.lengthas the inner loop's bound. The inner loop walks columns, so its bound ismoduleCurrents[0].length, notmoduleCurrents.length(the row count). - Expecting for-each to let you assign into a primitive 2D array.
for (double sample : moduleRow) { sample = 0.0; }changes nothing — same rule as 1D arrays. - Confusing row-major and column-major with "correct" and "incorrect." Neither is wrong — pick whichever matches what the question actually asks (per-module trends vs. per-instant totals).
Key Takeaways
- Traversing a full 2D array needs nested loops: outer for rows, inner for columns.
- Row-major order finishes one row before moving to the next; column-major order finishes one column (across all rows) before moving to the next — which one to use depends on what you're computing.
- A nested for-each loop traverses a 2D array with no indices; its outer variable is a 1D array (one row), its inner variable matches the element type.
- For-each can't modify a primitive 2D array's stored values, but it can call mutator methods on objects the array holds (like motor controllers).
Derived from csawesome-2026: unit-4-data-collections/4.12-2d-array-traversals-nested-loops.md (CSA 4.12)