Arrays (1D, 2D & Multidimensional)
An array is C's most basic way to store many values of the same type together — understanding exactly how they sit in memory is the foundation for pointers, strings, and structures, all coming up next in this track.
An array lets a program keep many values of the same type under one name, each one reachable by a numeric position instead of by a separate variable name. This article walks the idea from the ground up: declaring and filling a simple list of values, seeing exactly how those values sit side by side in memory, looping over them safely, and then stretching the same idea into grids (2D) and stacks of grids (3D and beyond) for tabular and multi-layered data.
Why Arrays Exist
Picture a program that has to record 100 sensor readings. Declaring reading1, reading2, all the way to reading100 would be unworkable — tedious to write, and impossible to loop over. An array solves this by giving one name to the whole batch and letting a numeric index pick out any single reading.
One declaration reserves storage for all 100 values, and a loop variable standing in for the index lets the same few lines fill and then print every one of them — something 100 separate variables could never offer.
#include <stdio.h>
int main() {
int readings[100];
for (int i = 0; i < 100; i++) {
readings[i] = i * 2;
}
for (int i = 0; i < 100; i++) {
printf("%d\n", readings[i]);
}
return 0;
}Declaring an Array
An array declaration names a type, a name, and how many elements to reserve, written as data_type array_name[size];.
This reserves room for exactly five integers, laid out one after another under the single name numbers, with valid positions 0 through 4.
int numbers[5];Indexing Starts at Zero
The first slot in any array is index 0, not index 1. For an array declared to hold five elements, the usable indices are 0, 1, 2, 3, and 4 — there is no index 5. The general rule: an array of size N has valid indices 0 through N minus 1.
It is easy to instinctively reach for numbers[5] on a 5-element array because 5 is the size you just typed — but that position was never reserved, and using it reads or writes memory the array does not own.
Reading and Writing Individual Elements
Once declared, each element behaves exactly like its own ordinary variable — it can appear on the left side of an assignment to store a value, or on the right side (or inside printf) to read one back.
numbers[0] = 10 stores into the first slot the same way an ordinary assignment would, and numbers[1] inside printf reads the value that was just stored in the second slot.
#include <stdio.h>
int main() {
int numbers[5];
numbers[0] = 10;
numbers[1] = 20;
printf("%d\n", numbers[1]);
return 0;
}Initializing an Array at Declaration
Values can be supplied right when the array is declared by listing them inside braces, separated by commas — they fill the slots left to right.
The five values fill the five reserved slots in order, so numbers[0] is 10 and numbers[4] is 50 without any loop needed just to set them up.
int numbers[5] = {10, 20, 30, 40, 50};Partial Initialization Leaves the Rest at Zero
Supplying fewer initial values than the declared size is perfectly legal — the slots that were mentioned get those values, and every remaining slot is automatically set to zero.
Only two values are listed, so numbers[0] becomes 10 and numbers[1] becomes 20, while numbers[2], numbers[3], and numbers[4] are all zeroed out for you.
int numbers[5] = {10, 20};Setting Every Element to Zero
A common shorthand for clearing an entire array is to initialize it with a single 0 in braces — the partial-initialization rule then zeroes out everything else, which in this case is the whole array.
Because only the first element is explicitly given (0), and unspecified elements are always zero-filled, all five elements end up 0.
int numbers[5] = {0};Letting the Compiler Count the Elements
When every value is supplied up front, the size in brackets can be left empty — the compiler simply counts how many values are in the braces and uses that as the size.
Four values appear in the braces, so this declaration is exactly equivalent to writing int numbers[4] = {10, 20, 30, 40}; explicitly.
int numbers[] = {10, 20, 30, 40};Arrays of Characters
Arrays are not limited to numbers — any single type can fill one, including char. A character array stores one character per slot, just like an int array stores one integer per slot.
Each element holds a single character. Character arrays are also the basis for how C represents text strings, which gets its own dedicated treatment elsewhere.
char letters[5] = {'A', 'B', 'C', 'D', 'E'};Looping Over an Array
Because element positions are numbered, a loop variable can double as the index — letting a handful of lines process every element regardless of how many there are.
As i counts up from 0 to 4, numbers[i] visits each element in turn, printing 10, 20, 30, 40, and 50 on their own lines — one loop body handles all five without repeating code.
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
printf("%d\n", numbers[i]);
}
return 0;
}Modifying Array Elements
An element can be overwritten at any time after the array is created, either by targeting one index directly or by looping through and updating every element with the same rule.
The first line changes only the third element directly. The loop afterward reaches every element in turn and doubles whatever value is already sitting there.
int numbers[5] = {10, 20, 30, 40, 50};
numbers[2] = 100;
for (int i = 0; i < 5; i++) {
numbers[i] = numbers[i] * 2;
}Filling an Array from User Input
Rather than hardcoding values, a program can ask the user for each one inside a loop, using scanf to store the typed number straight into the current index.
scanf needs the address of the variable it is writing into, so &numbers[i] hands it the address of whichever slot the loop currently points at — the same rule that applies to &-ing any ordinary variable before scanf.
#include <stdio.h>
int main() {
int numbers[5];
for (int i = 0; i < 5; i++) {
printf("Enter number %d: ", i + 1);
scanf("%d", &numbers[i]);
}
return 0;
}Worked Example: Sum of an Array
Adding up every element is a small but very common task: start an accumulator at zero, then let a loop add one element to it on every pass.
sum starts at 0 and picks up one more element each time through the loop, ending up as 10+20+30+40+50, so the program prints Sum = 150.
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum = sum + numbers[i];
}
printf("Sum = %d\n", sum);
return 0;
}Worked Example: Finding the Largest Element
A common pattern for finding the maximum is to assume the first element is the largest seen so far, then compare it against every element that follows, updating the assumption whenever a bigger value turns up.
Starting largest from numbers[0] instead of from 0 matters because the array could contain only negative values — seeding from a real element guarantees the starting point is always a value that could genuinely be the answer.
#include <stdio.h>
int main() {
int numbers[5] = {23, 67, 12, 89, 45};
int largest = numbers[0];
for (int i = 1; i < 5; i++) {
if (numbers[i] > largest) {
largest = numbers[i];
}
}
printf("Largest = %d\n", largest);
return 0;
}Array Bounds Are Never Checked Automatically
For an array declared as int numbers[5], the positions numbers[5], numbers[6], and numbers[-1] are all outside the reserved space, even though nothing stops the code from being written that way.
C performs no automatic bounds checking on array access. Reading or writing past the declared size compiles without complaint but produces undefined behavior — the result can be garbage values, corrupted neighboring data, or a crash, and it is entirely the programmer's job to stay inside the valid range.
The Classic Index-Loop Pattern
Most array-processing code in C boils down to one recurring shape: a counter that starts at 0, keeps going while it is less than the array's size, and increases by one each pass, with the array accessed through that counter inside the body.
This is the same skeleton behind summing, searching, printing, and modifying an array — only what happens inside the braces changes from task to task.
for (int i = 0; i < N; i++) {
// use array[i] here
}Two-Dimensional Arrays
A 2D array organizes data into rows and columns, like a small table or spreadsheet, declared as data_type array_name[rows][columns];.
This reserves a table with 3 rows and 4 columns, for 3 times 4 equals 12 individual int elements in total.
int matrix[3][4];Accessing a 2D Array Element
An element of a 2D array is reached with two indices in sequence — the first selects the row, the second selects the column within that row.
matrix[0][0] is the top-left element, matrix[1][2] is in the second row and third column, and matrix[2][3] sits in the last row and last column of a 3-by-4 table.
matrix[0][0];
matrix[1][2];
matrix[2][3];Initializing a 2D Array
A 2D array can be initialized with nested braces — an outer set of braces for the whole table, and one inner set of braces per row.
The first inner group fills row 0, the second fills row 1, so matrix[0][0] is 1, matrix[1][2] is 6, and every element lines up with its position in the nested braces.
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};Traversing a 2D Array with Nested Loops
Visiting every element of a table needs two loops nested inside each other — an outer loop that walks through the rows, and an inner loop that walks through the columns of whichever row is currently active.
The output is the matrix printed one row per line: 1 2 3, then 4 5 6 — the outer loop advances the row only after the inner loop has finished printing every column of the current row.
#include <stdio.h>
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 3; col++) {
printf("%d ", matrix[row][col]);
}
printf("\n");
}
return 0;
}What the Nested Loops Actually Do
Tracing the nested loops step by step: while row is 0, col counts 0, 1, then 2, visiting all three columns of the first row; only once that inner loop finishes does row advance to 1, and col again counts 0 through 2 for the second row. The total number of elements visited is always rows multiplied by columns.
Row-Major Storage Order
C does not scatter a 2D array's elements around — it lays every element out as one long run in memory, and it fills that run one entire row at a time before moving to the next row. This layout is called row-major order.
Even though the array reads as two separate rows in source code, the values 1 through 6 sit back to back in exactly that order — row 0 finishes completely before row 1 begins.
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
// Physical order in memory:
// 1 2 3 4 5 6
// |__row 0__| |__row 1__|How a 2D Array Sits in Memory
Because the whole table is really one contiguous run of elements, indexing with [row][column] is just a convenient way to describe a position inside that single run — the array should be thought of through its declared type and its two indices, not as some looser arbitrary structure.
Reading left to right shows the same six elements in the exact sequence they occupy in memory — row 0's three elements, immediately followed by row 1's three elements.
matrix[0][0] matrix[0][1] matrix[0][2] matrix[1][0] matrix[1][1] matrix[1][2]
[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]Partially Initializing a 2D Array
The same zero-fill rule that applies to a 1D array applies row by row to a 2D array: any element left out of an inner brace group is set to zero.
Row 0 becomes {1, 2, 0} because only two of its three values were given, and row 1 becomes {3, 0, 0} because only its first value was given.
int matrix[2][3] = {
{1, 2},
{3}
};Three-Dimensional Arrays
A 3D array adds a third dimension on top of rows and columns, declared as data_type array_name[x][y][z];. It helps to picture it as several 2D tables stacked one after another.
This reserves 2 times 3 times 4, or 24, individual int elements — two 3-by-4 tables stacked together.
int data[2][3][4];Accessing a 3D Array Element
A 3D array element needs three indices — one for which table in the stack, one for the row within that table, and one for the column within that row.
This reaches the element in the second table (index 1), third row (index 2), fourth column (index 3).
data[1][2][3];Traversing a 3D Array
Visiting every element of a 3D array extends the same nested-loop idea one level further, with a third loop nested inside the row-and-column pair.
i selects the table, j selects the row within it, and k selects the column within that row — the same nesting pattern would extend to a fourth or fifth loop for higher dimensions.
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
printf("%d ", data[i][j][k]);
}
}
}Arrays with More Than Three Dimensions
There is nothing special about stopping at three dimensions — the same bracket syntax keeps extending for however many dimensions a problem genuinely needs, though dimensions beyond 2D or 3D are far less common in everyday code.
a is 1D, b is 2D, c is 3D, and d is 4D — the number of bracket pairs in the declaration is what determines how many dimensions the array has.
int a[10];
int b[3][4];
int c[2][3][4];
int d[2][3][4][5];Counting Total Elements Across Dimensions
The total number of elements an array reserves is simply the product of every dimension's size, no matter how many dimensions there are.
Multiplying the sizes listed in the brackets gives the element count directly, whether the array has two dimensions, three, or more.
int data[2][3][4]; // 2 * 3 * 4 = 24 elements
int grid[2][3][4][5]; // 2 * 3 * 4 * 5 = 120 elementsMeasuring Array Size with sizeof
sizeof applied to a whole array gives its total size in bytes, while sizeof applied to one element gives that single element's size — dividing the two yields how many elements the array holds.
sizeof(numbers) reports the bytes used by all five ints together, and sizeof(numbers[0]) reports the bytes used by just one, so the division gives 5.
This trick only works where numbers still names the actual array object — once an array has been passed into a function as a parameter, this same expression no longer gives a meaningful count, for reasons covered later in this article and in more depth under Pointers.
#include <stdio.h>
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int count = sizeof(numbers) / sizeof(numbers[0]);
printf("Count = %d\n", count);
return 0;
}Using sizeof on a 2D Array
The same sizeof division extends to a 2D array, with a bit more care about which sizes are being compared to get rows, columns, or the total count.
sizeof(matrix[0]) treats the first row as its own array-sized chunk, which is what lets the second and third formulas separate rows from columns instead of only ever getting the flat total.
int matrix[3][4];
// total elements: sizeof(matrix) / sizeof(matrix[0][0]) = 12
// number of rows: sizeof(matrix) / sizeof(matrix[0]) = 3
// columns per row: sizeof(matrix[0]) / sizeof(matrix[0][0]) = 4Fixed-Size Versus Runtime-Sized Arrays
A declaration like int numbers[10]; bakes the size 10 permanently into the type itself. Some C standards and compilers additionally support variable-length arrays, where the size in brackets is an ordinary variable decided while the program is running.
Here the array's size is not known until scanf has read n, so the array's length is decided at runtime rather than being fixed in the source. Support for this feature is not universal across every C standard and compiler, so portable code should not rely on it without checking.
int n;
scanf("%d", &n);
int numbers[n];Arrays of Different Data Types
The type in front of the array name can be int, float, char, a struct, or any other single type — an array of struct elements works exactly the same way as an array of ints. The one rule that never bends is that every element inside one array shares that same declared type.
Why One Array Cannot Be Assigned to Another
Writing a = b; to copy one array's contents into another does not compile — an array name is not something that can appear on the left of a plain assignment. Copying element by element with a loop is the correct approach, and library facilities such as memcpy can also be used once their requirements for size and type are met.
The commented-out line is what does not compile; the loop below it is the standard way to actually copy every element from b into a.
int a[5], b[5] = {1, 2, 3, 4, 5};
// a = b; // INVALID — arrays cannot be assigned this way
for (int i = 0; i < 5; i++) {
a[i] = b[i];
}Comparing Arrays Element by Element
Writing if (a == b) does not compare the contents of the two arrays — it does not do what it looks like it should do here. Comparing contents requires walking through both arrays and checking corresponding elements one by one.
The flag equal starts true and is flipped to false the moment any pair of elements differs, with break stopping the loop early once a mismatch has already settled the answer.
int a[3] = {1, 2, 3};
int b[3] = {1, 2, 3};
int equal = 1;
for (int i = 0; i < 3; i++) {
if (a[i] != b[i]) {
equal = 0;
break;
}
}Passing Arrays to Functions
When an array is passed as a function argument, the parameter is adjusted to a pointer rather than staying a true array — which means the function has no way to recover how many elements the caller's array actually had just by looking at the parameter. That is why the element count is almost always passed alongside it as a separate argument.
size is supplied explicitly because arr alone cannot tell print_array how many elements numbers originally had. The full mechanics of why array parameters behave like pointers belongs to the Pointers topic.
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
int main() {
int numbers[4] = {1, 2, 3, 4};
print_array(numbers, 4);
return 0;
}The Off-by-One Loop Bound Mistake
One of the most common array bugs is writing a loop condition that lets the index reach one position past the end of the array.
The <= 5 version runs one extra time with i equal to 5, writing past the last valid index; changing the condition to < 5 keeps the loop confined to the array's real bounds.
int numbers[5];
// Wrong: writes to numbers[5], which does not exist
for (int i = 0; i <= 5; i++) {
numbers[i] = 0;
}
// Correct: stays within indices 0 through 4
for (int i = 0; i < 5; i++) {
numbers[i] = 0;
}Worked Example: Average of an Array
Computing an average combines the summing pattern with a division, and needs a cast to floating point so the division does not silently truncate to a whole number.
sizeof(marks) / sizeof(marks[0]) gives the element count without hardcoding 5 anywhere, and casting sum to double before dividing forces genuine floating-point division, printing Average = 80.00.
#include <stdio.h>
int main() {
int marks[5] = {80, 75, 90, 85, 70};
int size = sizeof(marks) / sizeof(marks[0]);
int sum = 0;
for (int i = 0; i < size; i++) {
sum = sum + marks[i];
}
double average = (double) sum / size;
printf("Average = %.2f\n", average);
return 0;
}Worked Example: Adding Two Matrices
Adding two matrices means adding each pair of elements that share the same row and column position, which is naturally expressed with the same nested-loop traversal seen earlier.
Each result[i][j] is filled with a[i][j] plus b[i][j], and printing it back out row by row produces 6 8 on the first line and 10 12 on the second.
#include <stdio.h>
int main() {
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] = a[i][j] + b[i][j];
}
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}Worked Example: Searching an Array
A linear search checks each element in turn against a target value, stopping as soon as a match is found and remembering whether one turned up at all.
found starts at 0 (false) and is set to 1 the moment a matching element is seen, with break ending the search immediately since there is nothing left to check after a match.
#include <stdio.h>
int main() {
int numbers[5] = {10, 25, 7, 40, 15};
int target = 40;
int found = 0;
for (int i = 0; i < 5; i++) {
if (numbers[i] == target) {
found = 1;
break;
}
}
if (found) {
printf("Found\n");
} else {
printf("Not found\n");
}
return 0;
}Arrays Occupy Contiguous Memory
For a declaration like int numbers[4] = {10, 20, 30, 40};, all four elements are placed back to back with no gaps between them, and every element occupies exactly the same amount of space as any other element of that type.
The precise numeric addresses involved depend on the platform and are not something a program should hardcode or rely on — what is guaranteed, and what actually matters, is that the elements sit contiguously, which is exactly why jumping straight to any index is fast.
What Indexing Really Selects
numbers[i] is simply a request for the i-th slot in that contiguous run of elements. Conceptually, C defines this subscripting in terms of moving a certain distance from the array's starting position — the same idea that connects arrays and pointers so closely, a relationship explored in full once the Pointers topic is reached.
Common Array Mistakes
Using an index outside the valid range, such as a[5] on an array declared with 5 elements, silently reads or writes memory the array was never given.
Writing a loop condition with <= instead of < against the array's size runs the loop one iteration too many, landing exactly on that same invalid index.
Assuming a function parameter like int arr[] still remembers the caller's original element count — it does not, since the parameter has already become a pointer by the time the function body runs.
Trying to copy one array into another with a straight assignment, a = b;, which does not compile because arrays are not assignable as whole objects.
Mixing up the row and column index in a 2D array, which quietly accesses a different, but still technically valid-looking, element than the one intended.
Best Practices for Working with Arrays
Keep every index inside the array's valid range, and default to a strict less-than comparison against the size in ordinary counting loops rather than less-than-or-equal. Give multidimensional arrays dimension names that say what they mean, such as ROWS and COLS, instead of unexplained numbers, and keep the declared size and every loop bound that touches the array consistent with each other.
Only rely on sizeof for an element count where the real array object is still directly in scope — not after it has been passed into a function. Pass the element count explicitly to any function that needs to know it. Take care with the order of dimensions in a multidimensional array so rows and columns are never accidentally swapped, avoid reserving arrays far larger than a program actually needs, initialize arrays on purpose rather than trusting whatever happens to already be in memory, and always remember that C will never stop out-of-bounds access on its own.
Quick Reference
A single place to glance back at the core declaration, access, and sizing syntax covered throughout this article.
1D declare: int numbers[5];
1D access: numbers[i]
2D declare: int matrix[3][4];
2D access: matrix[row][column]
3D declare: int data[2][3][4];
3D access: data[i][j][k]
Initialize: int a[3] = {10, 20, 30};
Infer size: int a[] = {10, 20, 30};
Zero-init: int a[5] = {0};
Element count: sizeof(a) / sizeof(a[0]) // only when a is the actual array object1D vs 2D vs Multidimensional at a Glance
Each extra pair of brackets adds one more dimension along which elements are organized, but the underlying idea — one name, many positions, reached by index — never changes.
An array is a fixed-size, ordered collection of elements of the same type, stored contiguously in memory and accessed by a zero-based index. A 2D array is effectively an array of arrays — think rows and columns.
Storing 100 related values as 100 separate variables would be unmanageable — an array lets you store them under one name and loop over them programmatically, which is essential for processing any real amount of data.
A list of student scores, a grid in a game (2D array), an image's pixel data (2D or 3D array), and a string itself (a char array) all rely on arrays.
An array is like a row of numbered mailboxes bolted together in a single block — mailbox 0, mailbox 1, mailbox 2, and so on. Because they're physically contiguous, if you know where mailbox 0 is, you can compute the exact location of mailbox 47 with simple arithmetic, without walking past every box in between.
A 1D array and a running sum Declares a fixed array of 5 scores, loops over every index to accumulate a sum, and prints the average.
int scores[5] = {90, 85, 77, 92, 88}; reserves space for exactly 5 contiguous integers and initializes each one in order — scores[0] is 90, scores[4] is 88.
sum = 0 starts an accumulator at zero before the loop begins.
The for loop runs with i from 0 to 4 (i < 5), reading scores[i] on each pass and adding it into sum.
After the loop, sum holds 90+85+77+92+88 = 432.
sum / 5.0 forces floating-point division (5.0, not 5), giving 86.4, which %.1f prints rounded to one decimal place.
Five ints laid out back to back, 4 bytes apart — scores[i]'s address is always scores[0]'s address + i*4.
A 2D array — rows and columns A 2D array is really one contiguous block underneath — grid[r][c] just computes the right offset into that block for you.
grid[2][3] reserves 6 contiguous ints total (2 rows × 3 columns), stored row by row: row 0's three elements, then row 1's three elements.
The outer loop (r) walks each row; the inner loop (c) walks each column within that row — this order matches how the data actually sits in memory.
grid[0][0], grid[0][1], grid[0][2] print '1 2 3', then a newline; grid[1][0], grid[1][1], grid[1][2] print '4 5 6' on the next line.
int scores[5] = {90,85,77,92,88}; for (int i = 0; i <= 5; i++) sum += scores[i];
int scores[5] = {90,85,77,92,88}; for (int i = 0; i < 5; i++) sum += scores[i];
<= 5 lets i reach 5, but the valid indices are only 0 through 4 — scores[5] reads memory just past the array's end, which is undefined behavior, not a safe out-of-range error.
void printAll(int arr[]) { int len = sizeof(arr) / sizeof(arr[0]); // wrong here } void printAll(int arr[], int len) { // len passed in explicitly }
Once an array is passed to a function, the parameter is really just a pointer — sizeof(arr) there reports the pointer's own size, not the original array's size. The element count must be passed in separately.
Array indices always start at 0, and the last valid index is size - 1 — accessing scores[5] on a 5-element array reads memory that doesn't belong to the array at all (undefined behavior, not a safe error).
An array's name, in most expressions, 'decays' into a pointer to its first element — this is the deep connection between arrays and pointers explored fully in the next topic.
A 2D array like int grid[3][4] is stored as one contiguous block of 12 integers in row-major order — meaning an entire row's elements are adjacent in memory before the next row begins.
C does not check array bounds at runtime — reading or writing outside an array's valid range compiles and often runs 'successfully' while silently corrupting nearby memory, one of C's most dangerous characteristics for beginners to internalize early.
The classic off-by-one: looping with i <= size instead of i < size, reading or writing one element past the array's actual end.
Assuming sizeof(array) gives you the number of elements — it gives you the total size in bytes; the element count is sizeof(array) / sizeof(array[0]), and this trick only works on the original array itself, not on a pointer that used to point to it.
Declaring a large array as a local (stack) variable inside a function and returning a pointer to it — the array's memory is reclaimed the moment the function returns, making the returned pointer dangle (a topic developed fully in Dynamic Memory Allocation and Memory Layout).
Confusing a 2D array's row-major layout when manually computing an index, like grid[i][j] vs. the equivalent flat index i * numCols + j — getting the multiplier wrong silently reads or writes the wrong element.
Iterating a 2D array with the outer loop over rows and inner loop over columns (matching row-major storage) for significantly better cache performance than iterating column-first.
Using a named constant (#define SIZE 5, covered in Preprocessor & Macros) for an array's size instead of a bare number, so the size only needs to change in one place.
Always double-checking a loop's bound is < size, not <= size, especially right after writing or modifying loop code — this single check prevents an enormous share of real C bugs.
Arrays are not pointers, even though they decay into one sizeof(array) inside the same scope where the array was declared correctly reports the array's full size, while sizeof(pointer) always reports the pointer's own size (commonly 8 bytes on 64-bit systems) — this distinction disappears the moment an array is passed into a function, because it decays into a plain pointer at the function boundary, losing all size information.
Variable-length arrays (VLAs) C99 introduced arrays whose size is determined at runtime rather than compile time — int arr[n]; where n is a variable. VLAs are convenient but allocate on the stack, so a large or user-controlled n can overflow the stack; they're also optional in C11 onward, meaning not every compiler supports them, which is why many production codebases avoid VLAs in favor of explicit dynamic allocation (the next major topic in this track).
What is the valid index range for an array declared as int arr[10]?
0 through 9 — arrays are always zero-indexed, and the highest valid index is always size - 1.
Does C perform bounds checking on array access?
No — reading or writing outside an array's declared bounds is undefined behavior in C. It may crash, silently corrupt other data, or appear to work by chance, with no guarantee either way.
How are 2D arrays actually stored in memory in C?
As one contiguous block, in row-major order — meaning all of row 0's elements come first in memory, followed by all of row 1's elements, and so on.
For an array declared to hold N elements, what is the valid range of indices?
0 through N minus 1 — there is no valid index equal to N.
If int scores[5] = {90, 85}; is written, what happens to the elements that were not given a value?
They are automatically set to zero, since C zero-fills any elements left out of an initializer list.
What does it mean that C stores a 2D array in row-major order?
It means every element of row 0 is stored in memory before any element of row 1 begins, so the whole table forms one contiguous run, row by row.
Why does writing a = b; to copy one array into another fail to compile?
Because an array name is not an assignable object in C — only individual elements can be copied, typically with a loop that copies each one in turn.
Why does a function that receives an array parameter usually also need to receive the element count as a separate argument?
Because the array parameter is adjusted to a pointer when it is passed in, so the function has no way to recover the original array's length from that parameter alone.
Write a C program to find the sum of all elements in an array of integers.
An accumulator variable starts at zero, and a loop adds one array element to it on each pass, leaving the accumulator holding the total once the loop finishes.
Write a C program to find the largest element in an array.
The first element seeds the initial guess for the largest value, and every later element that beats the current guess replaces it, leaving the true maximum once every element has been checked.
Write a C program to find the smallest element in an array.
This mirrors the largest-element approach but keeps the running value only when a smaller element appears, so the variable ends up holding the minimum instead of the maximum.
Write a C program that performs a linear search for a target value in an array and reports whether it was found.
The loop compares the target against each element in order and stops immediately once a match turns up, with a flag variable recording whether that ever happened.
Write a C program to reverse the elements of an array in place.
Swapping the element at each position with its mirror position from the opposite end, stopping halfway through, reverses the whole array without needing a second array to hold the result.
Write a C program to count how many even and how many odd numbers are in an array.
Checking the remainder of each element when divided by 2 sorts it into the even or odd bucket, and two separate counters tally how many fell into each.
Write a C program to add two 2x2 matrices and print the resulting matrix.
A pair of nested loops walks every row-and-column position once, adding the two matrices' corresponding elements into a third matrix, which is then printed row by row.
Write a C program to find the transpose of a matrix.
Transposing swaps each element's row and column position, so copying matrix[i][j] into transposed[j][i] while walking the original matrix builds the flipped table directly.
Write a C program to compute the average of the elements in an array of integers.
The elements are summed with an ordinary loop, and casting that sum to double before dividing by the element count ensures the result keeps its decimal portion instead of truncating.
1D array int a[5]; a single sequence of values
2D array int a[3][4]; a table of rows and columns
3D array int a[2][3][4]; several 2D tables stacked together
N-dimensional int a[..]..[..]; the same idea extended across more dimensions#include <stdio.h>
int main() {
int scores[5] = {90, 85, 77, 92, 88};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
printf("Average: %.1f\n", sum / 5.0);
return 0;
}#include <stdio.h>
int main() {
int grid[2][3] = { {1, 2, 3}, {4, 5, 6} };
for (int r = 0; r < 2; r++) {
for (int c = 0; c < 3; c++) {
printf("%d ", grid[r][c]);
}
printf("\n");
}
return 0;
}