Hello! Let's dive into our next lesson in the "Complex Pointer Types and Multi-Level Indirection" module.
In our last session, we built a dynamic 2D array using a pointer-to-a-pointer (int **). This gave us a flexible grid, but it had one limitation: every row had to be the same length. Today, we'll remove that constraint.
Our goal for this lesson is to implement and traverse a jagged array using an array of pointers. A jagged array is simply an array of arrays where each sub-array (or "row") can have a different length. This is an extremely useful and memory-efficient structure, especially in contexts like your work in embedded systems where you might need to handle collections of data with varying sizes, such as different length messages or configuration strings.
What is a Jagged Array?
Conceptually, a jagged array is an array where each element points to another array, and these pointed-to arrays can be of different sizes. This creates a "jagged" or irregular shape, unlike the neat rectangle of a standard 2D array.

The primary advantage of this structure is memory efficiency. If you need to store ten log messages, and some are 20 characters while others are 200, a jagged array allows you to allocate just enough space for each one, rather than wasting memory by allocating a 200-character buffer for every message.
The underlying C implementation for this is the same ** pointer structure we used in the last lesson. The difference lies in how we allocate and traverse the rows.
Implementing a Jagged Array of Integers
Let's start with a numeric example to focus on the mechanics. To create a jagged array, we need two key pieces of information:
- The number of rows.
- The size of each individual row.
This means that in addition to the int **matrix itself, we need a way to store the varying column counts. A simple integer array is perfect for this.
1. Allocation
Allocation is a multi-step process, very similar to our last lesson, but with a crucial change in the inner loop.
Let's say we want to create a jagged array with 3 rows of sizes 5, 10, and 3, respectively.
int rows = 3;
int row_sizes[] = {5, 10, 3};
int **jagged_array;
// Step 1: Allocate the array for the row pointers
jagged_array = (int **)malloc(rows * sizeof(int *));
if (jagged_array == NULL) { /* handle error */ }
// Step 2: Loop and allocate each row with its specific size
for (int i = 0; i < rows; i++) {
// Use row_sizes[i] to get the size for the current row
jagged_array[i] = (int *)malloc(row_sizes[i] * sizeof(int));
if (jagged_array[i] == NULL) { /* handle error and clean up */ }
}
Notice the key difference: malloc(cols * sizeof(int)) from our last lesson has become malloc(row_sizes[i] * sizeof(int)). The size of the allocation is now determined on a per-row basis.
2. Traversal
Because the rows have different lengths, you can no longer use a single cols variable in your inner loop. You must refer back to your row_sizes array to know the boundary for each row.
// Populate and print the jagged array
for (int i = 0; i < rows; i++) {
for (int j = 0; j < row_sizes[i]; j++) { // Inner loop bound is row_sizes[i]
jagged_array[i][j] = i * 100 + j;
printf("%-4d", jagged_array[i][j]);
}
printf("\n");
}
3. Deallocation
The deallocation process is identical to the one for a uniform dynamic 2D array. The free function doesn't need to know the size of the memory block it's freeing; it only needs the pointer returned by malloc. You still free in the reverse order of allocation.
// Step 1: Free each individual row
for (int i = 0; i < rows; i++) {
free(jagged_array[i]);
}
// Step 2: Free the array of pointers
free(jagged_array);
The Classic Example: An Array of Strings
The most common real-world use of a jagged array in C is an array of strings. Since strings are just arrays of characters (char *), an array of strings is an array of character pointers (char **). Each string can have a different length, making it a perfect fit for the jagged array model.

The following video provides an excellent walkthrough of dynamically allocating an array of strings, which is a practical application of implementing a jagged array.
Dynamically Allocate Memory For An Array Of Strings | C Programming Example
Watch the video 'Dynamically Allocate Memory For An Array Of Strings' from Portfolio Courses. It demonstrates the entire process of creating a char ** jagged array where the size and content of each string are determined at runtime.
Watch the video up to 11:58. Pay close attention to: The two-step allocation concept (01:14): first the array of pointers, then each string. The use of a temporary buffer to read user input (03:35). How the length of each input string is used to malloc just the right amount of memory (08:48). The use of strcpy to copy the string into its final, dynamically-allocated location (09:59). The video demonstrates creating and populating the array. The cleanup process (deallocation) would follow the same logic we just discussed: loop through the array to free each string, then free the array of pointers.
A Complete, Robust Implementation
To formalize this, let's look at some reference code that encapsulates the logic in functions. This approach is cleaner and less error-prone. The resource below provides a clear distinction between different methods of creating 2D arrays and correctly identifies the "Array of Pointers" method as being suitable for jagged arrays.
C Memory Management & Pointers - by Deepak Bijalwan
The article 'C Memory Management & Pointers' by Deepak Bijalwan contains an excellent, self-contained implementation for a dynamic 2D array using the array-of-pointers method.
Please review the section titled 'Method 1: Array of Pointers (Jagged Array)'. Memory Layout: First, examine the 'Detailed Memory Layout' diagram. It perfectly visualizes how the matrix pointer in the stack points to an array of pointers on the heap, and how those pointers, in turn, point to scattered row data blocks on the heap. Implementation: Study the functions create_2d_array_method1 and free_2d_array_method1. This is a production-quality implementation. Notice the robust error handling inside create_2d_array_method1: if an allocation for a row fails, it correctly frees all previously allocated rows before returning NULL. Although the example uses a constant cols, you can see that it could easily be modified to accept an array of column sizes, making it a true jagged array implementation.
Test your understanding!
Your task is to implement and test a pair of functions for creating and destroying a jagged integer array.
-
int** create_jagged_array(int rows, const int* row_sizes): This function should take the number of rows and a pointer to an array containing the size of each row. It must dynamically allocate the jagged array, perform error checking, and return theint**pointer. If anymallocfails, it should clean up all memory allocated so far and returnNULL. -
void free_jagged_array(int** array, int rows): This function should correctly deallocate all memory associated with the jagged array.
Use these functions in main to create, populate, print, and then free a jagged array with 3 rows of sizes 7, 4, and 9.
Show answer
#include <stdio.h>
#include <stdlib.h>
// Function to create a jagged array
int** create_jagged_array(int rows, const int* row_sizes) {
// Allocate the array of row pointers
int** array = (int**)malloc(rows * sizeof(int*));
if (array == NULL) {
perror("Failed to allocate row pointers");
return NULL;
}
// Allocate each individual row with its specific size
for (int i = 0; i < rows; i++) {
array[i] = (int*)malloc(row_sizes[i] * sizeof(int));
if (array[i] == NULL) {
perror("Failed to allocate row");
// Cleanup on failure
for (int j = 0; j < i; j++) {
free(array[j]);
}
free(array);
return NULL;
}
}
return array;
}
// Function to free the jagged array
void free_jagged_array(int** array, int rows) {
if (array == NULL) {
return;
}
for (int i = 0; i < rows; i++) {
free(array[i]); // Free each row
}
free(array); // Free the array of pointers
}
int main(void) {
int rows = 3;
int sizes[] = {7, 4, 9};
int** my_jagged_array = create_jagged_array(rows, sizes);
if (my_jagged_array == NULL) {
return 1;
}
printf("Jagged array created. Populating and printing:\n");
for (int i = 0; i < rows; i++) {
printf("Row %d (size %d): ", i, sizes[i]);
for (int j = 0; j < sizes[i]; j++) {
my_jagged_array[i][j] = i + j;
printf("%d ", my_jagged_array[i][j]);
}
printf("\n");
}
free_jagged_array(my_jagged_array, rows);
my_jagged_array = NULL;
printf("\nJagged array freed.\n");
return 0;
}
Conclusion
You have now extended your knowledge from dynamic 2D arrays to the more flexible jagged array. This is a powerful and memory-efficient pattern in C programming.
Key Takeaways:
- A jagged array is an array of pointers where each pointer can point to a sub-array of a different size.
- The primary benefit is memory efficiency, which is crucial in resource-constrained environments like embedded systems.
- Implementation requires a
**pointer and a mechanism to track the size of each row, typically a separate helper array. - Traversal requires using the specific size of each row to bound the inner loop.
- Deallocation follows the same reverse-order principle as regular dynamic 2D arrays: free each row, then free the array of pointers.
- The canonical example of a jagged array is a dynamically allocated array of strings (
char **).
Preview of the Next Lesson:
Our next topic is a perfect, real-world application of what you've just learned. We will explore how to manipulate command-line arguments (argc, argv) in the main function. As you'll see, argv is itself a jagged array (char **), provided to your program by the operating system when it starts.