Topic 63 of 100 Arrays and Strings in C Interactive Tutorial

Two-Dimensional Arrays in C: Rows, Columns, and Matrix Examples

12 mins read Last updated: 22 Aug 2026 23,400 views
C Compiler 0.00%

What is Two-Dimensional Arrays in C? Core Concept & Direct Summary

Core Objective & Problem Statement

How do 2D arrays represent matrices and tables in row-major memory order in C?

Direct Answer Summary

A one-dimensional array is a list — a single line of values. But plenty of real data isn't a list. It's a table: a class roster with students down the rows and subjects across the columns; a seating chart; an image where every pixel has a row and column. Data with two independent dimensions.

That's what a two-dimensional array gives you: an array of arrays, indexed by [row][column], forming a grid. It's the natural home for matrices, tables, boards, and anything you'd draw on graph paper — and the combination of 2D arrays with nested loops (which you already mastered) produces some of the most satisfying code in C.

(Suggested image: the hero graphic — a 3×4 grid of labeled cells, each showing its [row][col] index pair, e.g. [0][0], [0][1], ... [2][3])

How Does Two-Dimensional Arrays in C Work? In-Depth Mechanics & Architecture

What a 2D Array Actually Is

A 2D array in C is literally an array whose elements are arrays:

Read it carefully: grid is an array of 3 elements, and each element is an array of 4 ints. That's 3 rows × 4 columns = 12 integers total.

The mental model most people find easiest:

main.c
Run Online
col0 col1 col2 col3row0 [ 0 ][ 0 ][ 0 ][ 0 ]row1 [ 0 ][ 0 ][ 0 ][ 0 ]row2 [ 0 ][ 0 ][ 0 ][ 0 ]

The universal convention: first index = row, second index = column.grid[1][2] means row 1, column 2. Always in that order — mixing them up is the most common 2D confusion, and with a square matrix (3×3), the code will still compile and just produce transposed output.

(Suggested image: the 3×4 grid with arrows marking "first index → row (vertical)", "second index → column (horizontal)")


Declaration Forms

Basic:

main.c
Run Online
int grid[3][4];
// 3 rows, 4 columnsfloat prices[5][2];
// 5 rows, 2 columnschar board[8][8];
// chessboard!

Both dimensions need to be positive constant integers (or #defined values):

main.c
Run Online
#define ROWS 3
#define COLS 4
int grid[ROWS][COLS];
// recommended — readable and changeable centrally

Uninitialized 2D arrays contain garbage, exactly like 1D arrays. Not zeros.


Initialization: Four Ways

Way 1: Fully nested braces (clearest)

main.c
Run Online
int matrix[2][3] = {
{
        1, 2, 3
    }
    , {
        4, 5, 6
    }
}
;

Row by row, each row in its own braces. This is the form you should write in real code — it maps directly to the visual grid.

Way 2: Flat list (legal, but less clear)

main.c
Run Online
int matrix[2][3] = {
    1, 2, 3, 4, 5, 6
}
;
// same result — C fills row by row

Works because C stores 2D arrays row by row, so a flat list gets poured in order.

Way 3: Let the compiler count the first dimension

main.c
Run Online
int matrix[][3] = {
{
        1, 2, 3
    }
    , {
        4, 5, 6
    }
    , {
        7, 8, 9
    }
}
;
// → becomes matrix[3][3]

⚠️ Only the FIRST dimension may be omitted. The column count must always be specified:

main.c
Run Online
int bad[][];
// ✗ ERROR — compiler can't possibly figure this outint bad[3][];        // ✗ ERROR — column count requiredint good[][3] = {
...
}
;
// ✓ first dimension inferred

Why? Because to know where each row begins, the compiler must know how many columns there are. Row count is just "how many rows did you give me?" — countable from the braces. This rule comes back when you pass 2D arrays to functions.

Way 4: The zero-fill idiom (slightly different from 1D!)

main.c
Run Online
int grid[3][4] = {
    0
}
;
// ✓ ALL 12 elements set to 0

Note: 2D zero-fill uses a single brace set with one 0 — not {{0}}, not {{0,0,0,0},...}. It looks odd but works, and you'll see it constantly.

(Suggested image: side-by-side of the four forms with their resulting grids drawn out)


Accessing Elements

Two indexes, row first:

main.c
Run Online
#include <stdio.h>
int main(void) {
    int matrix[2][3] = {
{
            10, 20, 30
        }
        , {
            40, 50, 60
        }
    };
    printf("%d\n", matrix[0][0]);
    // 10  — top-left    printf("%d\n", matrix[0][2]);     // 30  — end of first row    printf("%d\n", matrix[1][0]);     // 40  — start of second row    printf("%d\n", matrix[1][2]);     // 60  — bottom-right
    matrix[1][1] = 99;
    // overwrite the center    printf("%d\n", matrix[1][1]);     // 99
    return 0;
}

The boundary rule, extended:

For int m[R][C]:

  • Valid row indexes: 0 to R - 1
  • Valid column indexes: 0 to C - 1

So matrix[2][3] (2 rows, 3 columns) permits rows 0–1 and columns 0–2. matrix[2][0] is out of bounds — and yes, C won't stop you, exactly as with 1D arrays.

(Suggested image: the same grid with valid rows/columns highlighted green and the out-of-bounds edges red)


Looping Through a 2D Array: Nested Loops, Naturally

This is where your nested-loops training pays off directly. Outer loop = rows, inner loop = columns:

main.c
Run Online
#include <stdio.h>
int main(void) {
    int grid[3][4] = {
{
            1, 2, 3, 4
        }
        , {
            5, 6, 7, 8
        }
        , {
            9, 10, 11, 12
        }
    };
    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 4; col++) {
            printf("%3d ", grid[row][col]);
        }
        printf("\n");
        // newline BETWEEN rows — outside the inner loop    }
        return 0;
    }

Output:

The structure maps perfectly onto the shape:

  • Outer loop moves down the rows
  • Inner loop walks across each row
  • printf("\n") sits between the loops — print it once per row, after the columns finish

That newline placement is the #1 2D output bug: put it inside the inner loop and every element lands on its own line; forget it entirely and everything prints in one endless line. Also note %3d — width formatting from our printf tutorial is what keeps the columns aligned, essential since values have different digit counts.

(Suggested image: the loops annotated over the grid — an arrow walking across a row labeled "inner loop", an arrow moving down labeled "outer loop")


Memory Layout: Row-Major Order

Understanding the physical storage explains a lot of 2D behavior (and later, pointers):

main.c
Run Online
int m[2][3] = {
{
        1,2,3
    }
    , {
        4,5,6
    }
}
;

In memory, it's not a grid — it's one flat sequence, row after row:

main.c
Run Online
Address: 1000 1004 1008 1012 1016 1020Value: 1 2 3 4 5 6Position: [0][0][0][1][0][2][1][0][1][1][1][2]

This is called row-major order (rows stored consecutively), and it's why:

  • A flat initializer ({1,2,3,4,5,6}) works — it's literally the same memory order
  • 2D arrays act like 1D arrays with computed indexes: element [r][c] sits at base + (r × COLS + c) × element_size
  • Iterating row-by-row is cache-friendly — the memory is already in that order

That formula, r × COLS + c, is also the secret behind 2D array pointer arithmetic, coming in a later cluster.

(Suggested image: the flat memory row compared with the visual grid, with arrows showing how row 1's values continue right after row 0's — this "grid is really a line" reveal is the most illuminating graphic on the page)


Input and Output for a Grid

main.c
Run Online
#include <stdio.h>
#define ROWS 2
#define COLS 3
int main(void) {
    int matrix[ROWS][COLS];
    // Input    puts("Enter the matrix values:");    for (int r = 0; r < ROWS; r++) {        for (int c = 0; c < COLS; c++) {            printf("matrix[%d][%d]: ", r, c);            scanf("%d", &matrix[r][c]);         // & needed — storing INTO the element        }    }
    // Output    puts("\nYour matrix:");    for (int r = 0; r < ROWS; r++) {        for (int c = 0; c < COLS; c++) {            printf("%5d", matrix[r][c]);        }        putchar('\n');    }
    return 0;
}

Sample Run:

main.c
Run Online
Enter the matrix values:matrix[0][0]: 1matrix[0][1]: 2matrix[0][2]: 3matrix[1][0]: 4matrix[1][1]: 5matrix[1][2]: 6
Your matrix: 1 2 3 4 5 6

Same rules as 1D: scanf needs &matrix[r][c] (the address of that slot), printf takes the value directly, and the newline goes between loops.


Matrix Operations: Where 2D Arrays Shine

Matrices are the classic 2D array application, and three operations cover most coursework.

1. Matrix Addition (element-wise)

Two matrices, same dimensions, add position by position:

main.c
Run Online
int a[2][2] = {
{
        1, 2
    }
    , {
        3, 4
    }
}
;
int b[2][2] = {
{
        5, 6
    }
    , {
        7, 8
    }
}
;
int sum[2][2];
for (int r = 0;
r < 2;
r++) {
    for (int c = 0;
    c < 2;
    c++) {
        sum[r][c] = a[r][c] + b[r][c];
        // position by position
    }
}
// sum = {
{
    6, 8
}
, {
    10, 12
}
}

Both matrices must be the same size — otherwise the operation is mathematically meaningless.

2. Transpose (rows become columns)

The transpose flips the matrix along its diagonal: result[c][r] = original[r][c].

main.c
Run Online
int m[2][3] = {
{
        1, 2, 3
    }
    , {
        4, 5, 6
    }
}
;
int t[3][2];
// dimensions SWAP!
for (int r = 0;
r < 2;
r++) {
    for (int c = 0;
    c < 3;
    c++) {
        t[c][r] = m[r][c];
        // indexes swapped
    }
}
// t = {
{
    1, 4
}
, {
    2, 5
}
, {
    3, 6
}
}

Output comparison:

main.c
Run Online
Original(2×3): Transpose(3×2): 1 2 3 1 4 4 5 6 2 5 3 6

Note the dimension swap: a 2×3 transpose becomes 3×2. The transpose is everywhere in computer graphics, data science, and linear algebra.

3. Matrix Multiplication (the triple loop)

The most requested matrix program — and the one that teaches you why 3 nested loops exist:

main.c
Run Online
int a[2][2] = {
{
        1, 2
    }
    , {
        3, 4
    }
}
;
int b[2][2] = {
{
        5, 6
    }
    , {
        7, 8
    }
}
;
int result[2][2];
for (int i = 0;
i < 2;
i++) {
    for (int j = 0;
    j < 2;
    j++) {
        result[i][j] = 0;
        // initialize the accumulator!        for (int k = 0;
        k < 2;
        k++) {
            result[i][j] += a[i][k] * b[k][j];
            // sum of products
        }
    }
}
// result = {
{
    19, 22
}
, {
    43, 50
}
}

The rule worth remembering: result[i][j] is the dot product of row i of A and column j of B — multiply matching elements and sum. Hence the inner k loop. And note the accumulator starts at 0 before the k loop runs — forget that, and you'll sum on top of garbage.

Dimension rule: A (m×n) × B (n×p) → result (m×p). The inner dimensions must match.

(Suggested image: the row-i × column-j highlight diagram for matrix multiplication — this exact visual is what makes triple-loop code make sense to students)


A Practical Example: Student Marks Table

Beyond matrices, 2D arrays model everyday tabular data beautifully:

main.c
Run Online
#include <stdio.h>
#define STUDENTS 4
#define SUBJECTS 3
int main(void) {
    int marks[STUDENTS][SUBJECTS];
    int studentTotal[STUDENTS] = {
        0
    };
    int subjectTotal[SUBJECTS] = {
        0
    };
    puts("Enter marks (student by student):");
    for (int s = 0; s < STUDENTS; s++) {
        printf("--- Student %d ---\n", s + 1);
        for (int sub = 0; sub < SUBJECTS; sub++) {
            printf("Subject %d: ", sub + 1);
            scanf("%d", &marks[s][sub]);
            studentTotal[s] += marks[s][sub];
            subjectTotal[sub] += marks[s][sub];
        }
    }
    puts("\n===== MARKS TABLE =====");
    printf("%-8s %6s %6s %6s %8s\n", "Student", "Sub1", "Sub2", "Sub3", "Total");
    for (int s = 0; s < STUDENTS; s++) {
        printf("%-8d", s + 1);
        for (int sub = 0; sub < SUBJECTS; sub++) {
            printf("%6d", marks[s][sub]);
        }
        printf("%8d\n", studentTotal[s]);
    }
    puts("------------------------------");
    printf("%-8s", "Total");
    for (int sub = 0; sub < SUBJECTS; sub++) {
        printf("%6d", subjectTotal[sub]);
    }
    putchar('\n');
    return 0;
}

Sample Run:

main.c
Run Online
--- Student 1 ---
Subject 1: 78
Subject 2: 85
Subject 3: 92
--- Student 2 ---
...
===== MARKS TABLE =====
Student Sub1 Sub2 Sub3 Total
1 78 85 92 255
2 65 70 80 215
3 90 88 95 273
4 55 62 58 175
------------------------------
Total 288 305 325

Look at the elegance: student totals come from summing across a row; subject totals come from summing down a column. Two different traversal directions, both natural with nested loops, and the row-and-column totals at the bottom of the table fall out almost automatically. This row-wise vs column-wise distinction is a genuinely useful mental tool for tabular data.

(Notably: the opposite traversal — outer loop on columns, inner on rows — is where you'd compute column-wise sums if you didn't accumulate inline. Both approaches are valid; the inline accumulator is easier to read.)


Passing 2D Arrays to Functions (A Preview)

This comes later in the cluster, but here's the shape so you recognize it:

main.c
Run Online
void printMatrix(int mat[][3], int rows) {
    // column size REQUIRED
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < 3; c++) {
            printf("%4d", mat[r][c]);
        }
        putchar('\n');
    }
}

You must specify the column count in the parameter (so the compiler can compute row offsets), while the row count can be passed separately. It's the same rule that made int bad[3][] illegal at declaration — the compiler always needs to know the row length.


Common 2D Array Mistakes

  1. Swapping the indexes — m[c][r] instead of m[r][c]. On a square matrix it compiles and silently gives you the transpose. Row first, always.
  2. Newline in the wrong place — printf("\n") belongs between the loops, not inside the inner one.
  3. Omitting the column size — int bad[3][] or void f(int m[][]) won't compile; the column count is mandatory everywhere.
  4. Confusing rows/columns in loops — outer loop should match the first dimension. When output looks "rotated," check which loop bounds use ROWS vs COLS.
  5. Using = {0, 0} or {{0}} for zero-fill — = {0} is the correct full-fill idiom; other forms may leave rows partially unset.
  6. Forgetting to initialize the accumulator in matrix multiplication — result[i][j] += ... needs result[i][j] = 0; first, or you're adding onto garbage.
  7. Nested loop off-by-one — same boundary discipline as 1D: row < ROWS, col < COLS. C won't catch violations.

(Suggested image: "7 2D Array Mistakes" checklist graphic — consistent with the series)


Final Thoughts

Two-dimensional arrays are just arrays of arrays — but that one extra dimension turns a list into a table, and a table into a matrix, a board, a screen, or a spreadsheet. The whole topic rests on a handful of ideas: row then column, outer loop for rows and inner for columns, row-major memory order, and neither dimension is checked for you.

The matrix triple-loop (multiplication) is the one to practice hardest — it's the canonical 2D exercise in every course, and once you can write it from the "row i · column j" rule rather than from memory, you've genuinely internalized how 2D traversal works.

Next in this cluster: passing arrays to functions, where the row and column size rules you just learned get their full explanation.


Runnable C Code Example & Practical Implementation

main.c
Run Online
#include <stdio.h>
int main() {
    int matrix[2][3] = {
{
            1, 2, 3
        }
        ,
{
            4, 5, 6
        }
    };
    printf("2D Matrix Elements:\
");
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 3; c++) {
            printf("%d ", matrix[r][c]);
        }
        printf("\
");
    }
    return 0;
}

Key Takeaways, Best Practices & Memory Safety Rules

Best Practice & Key Takeaways
Always specify column bounds in function parameters when passing 2D arrays (int arr[][COLS]).

Frequently Asked Questions & Technical Interview Insights

Technical Interview & Viva Questions

Frequently asked C programming interview questions
Q

Q: Why MUST column dimensions be specified when declaring or passing a 2D array (`void func(int arr[][3])`)?

A: The compiler needs the column dimension to calculate memory jump offsets when indexing rows ($\\text{row} \\times \\text{num\\_cols}$).
Q

Q: What is the difference between an array of pointers and a 2D array?

A: A 2D array is one contiguous memory block; an array of pointers stores separate memory addresses that can point to non-contiguous rows in different parts of memory.

Related Tools, Tutorials & Authoritative References

Recommended Interactive Tools

Authoritative Standards & Citations