dev only

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.length as the inner loop's bound. The inner loop walks columns, so its bound is moduleCurrents[0].length, not moduleCurrents.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)