Multi-Dimensional Arrays in C Programming (Complete Guide with Examples – 2026)

Multi-Dimensional Arrays in C Programming (2D, 3D Arrays Explained with Examples)

1. Introduction

If you are learning C programming, one topic you absolutely cannot skip is multidimensional arrays. From storing student grades in a table to building a game board, from image processing to matrix operations in engineering, multidimensional arrays are everywhere. Yet many beginners struggle with them. Why? Because understanding how they work in memory, how to traverse them efficiently, and how to pass them to functions requires a solid mental model — not just syntax memorisation.

This guide provides that foundation. By the end, you will understand:

  • What multidimensional arrays are and how they work in memory.
  • How to declare, initialize, and access 2D and 3D arrays.
  • How C stores them using row-major order.
  • How to pass them to functions correctly.
  • How to allocate them dynamically.
  • Real-world examples and common mistakes to avoid.

Let’s dive in.

Prerequisites

Basic knowledge of C variables, data types, and functions will help. This guide is self-contained, but some familiarity with code is required.

C Language Fundamentals: A Smart Way to Understand Variables, Constants, Keywords & More

Data Types in C Programming: int, float, char Explained

Learn Functions in C with Real-Life Coding Examples (2026 Guide)

2. What is a multidimensional array?

A multidimensional array in C is an array that has more than one dimension. The simplest and most common form is the two-dimensional (2D) array, which you can think of as a table or matrix with rows and columns. Think of it this way:

  • A 1D array is a single row of boxes.
  • A 2D array is a grid of boxes (rows x columns).
  • A 3D array is multiple grids stacked on top of each other (like pages in a book).

Multidimensional arrays are not a separate data structure — they are just arrays whose elements are themselves arrays. C handles them as one flat block of memory, which is the key to understanding their performance and behaviour.

3. Declaring a 2D array in C

The syntax for declaring a 2D array is:

data_type array_name[rows][columns];

Examples
int matrix[3][4];
float grid[5][5];
char board[8][8];

Here, matrix[3][4] means 3 rows and 4 columns, giving a total of 3 x 4 = 12 elements.

Important: All sizes must be known at compile time for a regular (static) array. For runtime sizes, you need dynamic allocation (covered in the upcoming section).

Initializing a 2D array

You can initialize a 2D array at the time of declaration in several ways.

Method 1 – With nested braces (recommended for clarity)

int marks[3][3] = {
  {90, 85, 78};      // Row 0
  {88, 92, 76};      // Row 1
  {70, 65, 80}       // Row 2
};

Method 2 – In a single line

int marks[3][3] = {90, 85, 78, 88, 92, 76, 70, 65, 80};

Both methods produce the same result. Method 1 is clearer and less error-free.

Method 3 – Partial initialization

int matrix[3][3] = {
    {1, 2},     // Row 0: third element auto-set to 0
    {4},        // Row 1: second and third auto-set to 0
    {}          // Row 2: all zeros
};

C automatically fills unspecified elements with 0.

Method 4 – Zero-initialize everything

int grid[4][4] = {0};   // All 16 elements set to 0

4. Accessing Elements of a 2D array

Elements are accessed using two indices: array[row][column]. Both are zero-based.

int marks[3][3] = {
    {90, 85, 78},
    {88, 92, 76},
    {70, 65, 80}
};

printf("%d\n", marks[0][0]);  // 90  — Row 0, Column 0
printf("%d\n", marks[1][2]);  // 76  — Row 1, Column 2
printf("%d\n", marks[2][1]);  // 65  — Row 2, Column 1

You can also assign values after declaration:

int grid[2][2];
grid[0][0] = 10;
grid[0][1] = 20;
grid[1][0] = 30;
grid[1][1] = 40;

5. How C stores 2D arrays in memory (row-major order)

This is the most important concept to understand about multidimensional arrays in C. C stores 2D arrays in row-major order. This means all elements of row 0 are stored first, followed by all elements of row 1, and so on — in one flat, contiguous block of memory. There are no gaps.

Example:

For int grid[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}

Memory layout (assuming base address 1000, int = 4 bytes):

Address   Value    Element
1000      1        grid[0][0]
1004      2        grid[0][1]
1008      3        grid[0][2]
1012      4        grid[1][0]
1016      5        grid[1][1]
1020      6        grid[1][2]
1024      7        grid[2][0]
1028      8        grid[2][1]
1032      9        grid[2][2]
The address formula
address of grid[i][j] = base + (i * num_cols + j) * sizeof(int)

Why does this matter?

Because of row-major order, always put the column index in the inner loop when traversing. This accesses memory sequentially, which is cache-friendly and significantly faster on large arrays.

// FAST — sequential memory access (cache-friendly)
for (int i = 0; i < rows; i++)
    for (int j = 0; j < cols; j++)
        process(grid[i][j]);

// SLOW — jumps through memory (cache-unfriendly)
for (int j = 0; j < cols; j++)
    for (int i = 0; i < rows; i++)
        process(grid[i][j]);

6. Traversing a 2D array

The standard way to traverse a 2D array is with nested for loops.

Printing all elements

int main() {
    int matrix[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };

    int rows = 3, cols = 4;

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%4d", matrix[i][j]);
        }
        printf("\n");
    }

    return 0;
}
Output
   1   2   3   4
   5   6   7   8
   9  10  11  12

Reading input into a 2D array

int grid[3][3];

printf("Enter 9 values:\n");
for (int i = 0; i < 3; i++)
    for (int j = 0; j < 3; j++)
        scanf("%d", &grid[i][j]);

Finding the sum of all elements

int sum = 0;
for (int i = 0; i < rows; i++)
    for (int j = 0; j < cols; j++)
        sum += matrix[i][j];

printf("Sum = %d\n", sum);

Finding the largest element

int max = matrix[0][0];
for (int i = 0; i < rows; i++)
    for (int j = 0; j < cols; j++)
        if (matrix[i][j] > max)
            max = matrix[i][j];

printf("Max = %d\n", max);

7. Passing a 2D Array to a Function

This is where many beginners get confused. When passing a 2D array to a function, you must specify the number of columns — the first dimension (rows) can be omitted, but all other dimensions are required. The compiler needs the column count to calculate memory offsets correctly.

Correct syntax

#include <stdio.h>

// Columns MUST be specified
void printMatrix(int arr[][4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%4d", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int m[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printMatrix(m, 3);
    return 0;
}

Alternative using a pointer to array

void printMatrix(int (*arr)[4], int rows) {
    // Same behavior — arr is a pointer to a row of 4 ints
}

Alternative using C99 Variable-Length Arrays (VLA)

void printMatrix(int rows, int cols, int arr[rows][cols]) {
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            printf("%4d", arr[i][j]);
}

The VLA approach is the most flexible — it lets you pass any size without hardcoding the column count. It requires C99 or later (gcc -std=c99).

Key Rule

Always pass the number of rows and columns explicitly. Never rely on sizeof inside the function — by then, the array has decayed and sizeof gives the pointer size, not the array size.

8. 3D array in C

A 3D array adds a third dimension — you can think of it as multiple 2D matrices stacked together, like the pages of a book.

Declaration

int cube[layers][rows][cols];

Example
int cube[2][3][3] = {
    // Layer 0
    {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    },
    // Layer 1
    {
        {10, 11, 12},
        {13, 14, 15},
        {16, 17, 18}
    }
};

// Access: cube[layer][row][column]
printf("%d\n", cube[0][1][2]);  // Output: 6
printf("%d\n", cube[1][2][0]);  // Output: 16

Traversing a 3D array

for (int l = 0; l < 2; l++) {
    printf("Layer %d:\n", l);
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%4d", cube[l][i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

Memory formula for 3D arrays

address of cube[l][i][j] = base + (l*R*C + i*C + j) * sizeof(type)

Where R = number of rows, C = number of columns.

Warning

3D arrays grow very fast. A float data[100][100][100] array holds 1,000,000 floats = approximately 4 MB. This will cause a stack overflow as it is declared as a local variable. Use static or malloc for large arrays.

9. Dynamic multidimensional arrays

Static arrays require sizes known at compile time. When you need sizes determined at runtime, use dynamic allocation with malloc.

Method 1 – Array of pointers (flexible / jagged)

This approach allocates each row separately. Rows can even have different lengths (jagged arrays).

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 4;

    // Step 1: Allocate an array of row pointers
    int **matrix = (int **)malloc(rows * sizeof(int *));

    // Step 2: Allocate each row
    for (int i = 0; i < rows; i++)
        matrix[i] = (int *)malloc(cols * sizeof(int));

    // Use it exactly like a regular 2D array
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            matrix[i][j] = i * cols + j + 1;

    // Print
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++)
            printf("%4d", matrix[i][j]);
        printf("\n");
    }

    // Step 3: Free each row, then the pointer array
    for (int i = 0; i < rows; i++)
        free(matrix[i]);
    free(matrix);

    return 0;
}

Output
   1   2   3   4
   5   6   7   8
   9  10  11  12

Method 2 – Single malloc (contiguous, faster)

Allocates all elements in one block. This is faster due to better cache performance and simpler to free.

int rows = 3, cols = 4;

// One block for everything
int *matrix = (int *)malloc(rows * cols * sizeof(int));

// Access element [i][j] with manual indexing
matrix[i * cols + j] = value;

// Free with a single call
free(matrix);

Which method to choose?

Method 1 (array of pointers)Method 2 (single malloc)
Syntaxmatrix[i][j]matrix[i * cols + j]
MemoryNon-contiguousContiguous
Cache performanceSlowerFaster
Jagged rowsYesNo
Free callsrows + 11

For performance-critical code (image processing, scientific computing), always prefer Method 2.

10. Real-world example – matrix multiplication

Let’s apply everything with a complete, practical example. Matrix multiplication is a foundational operation in linear algebra, graphics, and machine learning.

Formula

For matrices A(n * k) and B(k * m), result C(n * m):

C[i][j] = sum of A[i][p] * B[p][j]  for p = 0 to k-1
#include <stdio.h>
#include <stdlib.h>

// Allocate an n×m matrix, initialized to 0
int **createMatrix(int n, int m) {
    int **mat = (int **)malloc(n * sizeof(int *));
    for (int i = 0; i < n; i++)
        mat[i] = (int *)calloc(m, sizeof(int));
    return mat;
}

// Free a matrix
void freeMatrix(int **mat, int n) {
    for (int i = 0; i < n; i++)
        free(mat[i]);
    free(mat);
}

// Print a matrix
void printMatrix(int **mat, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++)
            printf("%6d", mat[i][j]);
        printf("\n");
    }
}

// Multiply A(n×k) × B(k×m) → C(n×m)
int **multiply(int **A, int **B, int n, int k, int m) {
    int **C = createMatrix(n, m);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            for (int p = 0; p < k; p++)
                C[i][j] += A[i][p] * B[p][j];
    return C;
}

int main() {
    // A = 2×3 matrix
    int **A = createMatrix(2, 3);
    int valA[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 3; j++)
            A[i][j] = valA[i * 3 + j];

    // B = 3×2 matrix
    int **B = createMatrix(3, 2);
    int valB[6] = {7, 8, 9, 10, 11, 12};
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 2; j++)
            B[i][j] = valB[i * 2 + j];

    printf("Matrix A:\n");
    printMatrix(A, 2, 3);

    printf("\nMatrix B:\n");
    printMatrix(B, 3, 2);

    int **C = multiply(A, B, 2, 3, 2);

    printf("\nResult C = A × B:\n");
    printMatrix(C, 2, 2);

    freeMatrix(A, 2);
    freeMatrix(B, 3);
    freeMatrix(C, 2);

    return 0;
}

Output
Matrix A:
     1     2     3
     4     5     6

Matrix B:
     7     8
     9    10
    11    12

Result C = A × B:
    58    64
   139   154

11. Common mistakes to avoid

Mistake 1 – off-by-one error

int arr[3][3];
arr[3][0] = 5;  // WRONG — valid indices are 0, 1, 2

Arrays are zero-indexed. arr[3] is out of bounds — undefined behaviour.

Mistake 2 – Wrong loop order (cache performance issue)

// SLOW — avoid this for large arrays
for (int j = 0; j < cols; j++)
    for (int i = 0; i < rows; i++)
        process(arr[i][j]);

Always iterate rows in the outer loop and columns in the inner loop.

Mistake 3 – Forgetting to free dynamic arrays

int **matrix = allocate(3, 4);
// ... use matrix ...
// Missing: free each row, then the matrix itself

Always free in reverse order of allocation. Use tools like Valgrind to detect memory leaks.

Mistake 4 – Omitting column size in function parameters

void process(int arr[][], int rows) { }  // WRONG — compiler error
void process(int arr[][4], int rows) { } // CORRECT

Mistake 5 – Forgetting nested loops

Leads to incomplete access to data

Mistake 6 – Stack overflow with large arrays

int bigData[1000][1000][10];  // ~40 MB on stack — crash!

Use static or malloc for large multidimensional arrays.

Mistake 7 – Using sizeof inside a function

void func(int arr[][4], int rows) {
    int size = sizeof(arr);  // Returns pointer size, NOT array size!
}

Always pass dimensions explicitly as function parameters.

12. Conclusion

Multidimensional arrays are one of the most powerful and widely used features in C programming. Understanding them deeply — not just the syntax, but the memory model behind them — is what separates a good C programmer from a great one. Here is a quick recap of everything we covered:

  • A 2D array in C is declared as type name[rows][cols] and accessed with name[i][j].
  • C stores all multidimensional arrays in row-major order — a single flat block of memory, row by row.
  • Always iterate with the column index in the inner loop for cache-friendly performance.
  • When passing a 2D array to a function, you must always specify the column size.
  • Use malloc for dynamic 2D arrays; prefer a single allocation for performance-critical code.
  • Always free dynamically allocated arrays when done, and always pass dimensions explicitly to functions

The best way to solidify this knowledge is to write code. Build a tic-tac-toe board, implement a grade tracker, or write your own matrix library. The more you use multidimensional arrays, the more natural they become.

Now that you understand how arrays work in memory, the next step is to understand:

  • How pointers interact with arrays.
  • Why arrays behave like pointers
  • When to use each.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top