Arrays in C Explained — Declaration, Indexing, and Common Pitfalls
Every real program deals with collections of data. A weather app tracks 30 days of temperatures. A game tracks the scores of 8 players. A bank tracks thousands of account balances. If C didn't give us a way to store multiple values under a single name, you'd have to write temperature_day1, temperature_day2, temperature_day3... all the way to temperature_day30. That is not programming — that's madness.
Arrays solve this exact problem. They let you group multiple values of the same type under one variable name and access any individual value instantly using its position number. Instead of 30 separate variables, you get one array with 30 slots. Instead of writing 30 lines to print each temperature, you write one loop. That is the power arrays hand you — and it's the foundation of almost every data structure you'll ever learn.
By the end of this article you'll know how to declare and initialize an array in C, read from it and write to it using indexes, loop through every element with a for loop, understand how arrays sit in memory, and avoid the two bugs that trip up almost every beginner on their first week. Let's build this up from zero.
Declaring and Initializing Your First C Array
Declaring an array in C follows a simple pattern: you give the data type, a name, and the number of slots you need in square brackets. That's it.
The syntax looks like this: int scores[5]; — this tells C to reserve 5 consecutive slots in memory, each big enough to hold one int, and label the whole row 'scores'. Nothing is stored in them yet — they hold garbage values until you assign something.
You can also declare and fill the array at the same time using an initializer list — curly braces with values separated by commas. When you do this, C lets you leave out the size entirely and figures it out for you by counting the values you provided.
One thing to burn into memory right now: C arrays are zero-indexed. The first element lives at index 0, the second at index 1, and so on. An array of size 5 has valid indexes 0, 1, 2, 3, and 4. Index 5 does not exist. Accessing it is the single most common bug in C programming, and we'll come back to it in the gotchas section.
Let's declare an array of 5 student test scores, initialize it with real values, and print each one.
#include <stdio.h> int main() { // Declare an integer array that holds 5 test scores. // The number in brackets is the SIZE — total number of slots. int scores[5] = {88, 92, 75, 100, 63}; // Access individual elements using their INDEX (position number). // Indexes start at 0, so the first score is scores[0], not scores[1]. printf("First student score: %d\n", scores[0]); // prints 88 printf("Second student score: %d\n", scores[1]); // prints 92 printf("Last student score: %d\n", scores[4]); // prints 63 — index 4, not 5! // You can also change a value after declaration by assigning to a specific index. scores[2] = 80; // Student 3 got a re-grade — update index 2 from 75 to 80 printf("Updated third score: %d\n", scores[2]); // prints 80 return 0; }
Second student score: 92
Last student score: 63
Updated third score: 80
Looping Through an Array — The Real Power Unlocked
Accessing elements one by one with hardcoded indexes only works when your array is tiny and you already know every position you need. The real strength of arrays shows up the moment you combine them with a loop.
A for loop and an array are a natural pair. The loop counter acts as the index — it starts at 0, goes up by 1 each iteration, and stops before it reaches the array's length. This pattern is so standard in C that you'll write it thousands of times in your career.
The critical ingredient here is knowing the array's length. In C, arrays don't carry their own size around — unlike some other languages. The standard trick is to calculate the size at the point where the array is declared using the sizeof trick: sizeof(array) / sizeof(array[0]). This divides the total bytes the array occupies by the bytes one element occupies, giving you the element count. This only works in the same scope where the array was declared — we'll cover why in the gotchas.
Let's build a real example: store seven daily temperatures for a week, print them all, and calculate the average — something a weather app genuinely does.
#include <stdio.h> int main() { // Seven daily temperatures in Celsius for one week. // Using an initializer list — C counts the values and sets the size to 7. float daily_temps[] = {18.5, 21.0, 19.3, 23.8, 22.1, 17.6, 20.4}; // sizeof(daily_temps) gives total bytes the whole array uses. // sizeof(daily_temps[0]) gives bytes for ONE float element. // Dividing them gives us the number of elements — 7 in this case. int num_days = sizeof(daily_temps) / sizeof(daily_temps[0]); float total = 0.0; // Running total so we can calculate the average printf("Daily temperatures this week:\n"); printf("-----------------------------\n"); // The loop counter 'day' doubles as the array index. // Condition is day < num_days (NOT <=) to stay within valid indexes. for (int day = 0; day < num_days; day++) { printf("Day %d: %.1f°C\n", day + 1, daily_temps[day]); // day+1 in the print just makes it human-readable (Day 1, not Day 0) total += daily_temps[day]; // Add each temperature to the running total } float average = total / num_days; // Divide total by number of days printf("-----------------------------\n"); printf("Weekly average: %.2f°C\n", average); return 0; }
-----------------------------
Day 1: 18.5°C
Day 2: 21.0°C
Day 3: 19.3°C
Day 4: 23.8°C
Day 5: 22.1°C
Day 6: 17.6°C
Day 7: 20.4°C
-----------------------------
Weekly average: 20.39°C
How Arrays Live in Memory — Why This Changes Everything
This section is where things get genuinely interesting — and where C separates itself from beginner-friendly languages. Understanding this will make you a better C programmer immediately.
When you declare int scores[5], C goes to RAM and finds 5 consecutive (side-by-side) memory addresses and reserves them all for you. On most systems, an int is 4 bytes, so your array occupies 20 bytes in a row. Think of it like booking 5 consecutive seats on a train — not 5 seats scattered randomly, but 5 in a row.
This matters because of how fast array access is. When you write scores[3], C doesn't search for element 3. It calculates the exact memory address mathematically: start_address + (3 × size_of_one_element). That's a single calculation — instant access regardless of whether you're accessing element 0 or element 499. This is called O(1) access time and it's why arrays are so fundamental.
The array name itself — scores, without brackets — is actually the memory address of the very first element. This is the bridge between arrays and pointers, which you'll explore later. For now, just know that when you pass an array to a function, you're passing this starting address, not a copy of all the data. That's why sizeof won't give you the element count inside a function that received the array as a parameter.
#include <stdio.h> int main() { // Declare a simple integer array with 5 values int scores[5] = {88, 92, 75, 100, 63}; printf("Exploring how scores[] sits in memory:\n"); printf("---------------------------------------\n"); // %p is the format specifier for printing a memory address (pointer) printf("Address of the entire array (scores): %p\n", (void*)scores); printf("Address of first element (scores[0]): %p\n", (void*)&scores[0]); // These two lines print THE SAME address — the array name IS the first element's address printf("\nElement-by-element address breakdown:\n"); for (int i = 0; i < 5; i++) { // &scores[i] gives the memory address of element at index i // Notice each address is exactly 4 bytes higher than the previous one printf(" scores[%d] = %3d stored at address: %p\n", i, scores[i], (void*)&scores[i]); } // sizeof the full array vs sizeof one element printf("\nTotal bytes used by scores[]: %zu\n", sizeof(scores)); printf("Bytes per element (one int): %zu\n", sizeof(scores[0])); printf("Number of elements: %zu\n", sizeof(scores) / sizeof(scores[0])); return 0; }
---------------------------------------
Address of the entire array (scores): 0x7ffd5e2a1b20
Address of first element (scores[0]): 0x7ffd5e2a1b20
Element-by-element address breakdown:
scores[0] = 88 stored at address: 0x7ffd5e2a1b20
scores[1] = 92 stored at address: 0x7ffd5e2a1b24
scores[2] = 75 stored at address: 0x7ffd5e2a1b28
scores[3] = 100 stored at address: 0x7ffd5e2a1b2c
scores[4] = 63 stored at address: 0x7ffd5e2a1b30
Total bytes used by scores[]: 20
Bytes per element (one int): 4
Number of elements: 5
Passing Arrays to Functions — The Pattern You'll Use Everywhere
Almost no real program does everything in main(). You'll want to break work into functions — one to fill an array with user input, one to find the maximum value, one to print results. So you need to know how to pass an array to a function correctly.
Here's the rule: you can't pass an array by value in C. When you write the array name as a function argument, C automatically passes the memory address of the first element. The function receives a pointer, not a copy. This means the function can read and modify the original array's contents.
The function signature accepts the array as int array_name[] or equivalently int *array_name — both mean the same thing to the compiler. Because the function only receives a pointer, it has no way of knowing how many elements the array has. This is why you always pass the length as a separate int parameter. It's not a workaround — it's the correct C idiom.
Let's write a complete example with three separate functions: one to print an array, one to find the highest value, and one to reverse the array in place. This mirrors real utility code you'd write on the job.
#include <stdio.h> // Prints every element of the array on one line. // 'length' must be passed because the function has no other way to know the size. void print_array(int numbers[], int length) { printf("[ "); for (int i = 0; i < length; i++) { printf("%d", numbers[i]); if (i < length - 1) printf(", "); // Don't print a trailing comma after the last element } printf(" ]\n"); } // Finds and returns the largest value in the array. // Starts by assuming the first element is the largest, then checks the rest. int find_maximum(int numbers[], int length) { int largest = numbers[0]; // Assume first element is the max to start for (int i = 1; i < length; i++) { // Start at index 1 — we already have index 0 if (numbers[i] > largest) { largest = numbers[i]; // Found a new champion — update largest } } return largest; } // Reverses the array IN PLACE — modifies the original, no copy made. // Uses a two-pointer technique: swap elements from both ends moving inward. void reverse_array(int numbers[], int length) { int left = 0; // Start index (left side) int right = length - 1; // End index (right side) while (left < right) { // Classic swap using a temporary variable int temp = numbers[left]; numbers[left] = numbers[right]; numbers[right]= temp; left++; // Move left pointer inward right--; // Move right pointer inward } } int main() { int quiz_scores[] = {74, 91, 55, 88, 100, 67, 83}; int count = sizeof(quiz_scores) / sizeof(quiz_scores[0]); // 7 elements printf("Original scores: "); print_array(quiz_scores, count); int top_score = find_maximum(quiz_scores, count); printf("Highest score: %d\n", top_score); reverse_array(quiz_scores, count); // This modifies quiz_scores directly printf("Reversed scores: "); print_array(quiz_scores, count); return 0; }
Highest score: 100
Reversed scores: [ 83, 67, 100, 88, 55, 91, 74 ]
| Aspect | Individual Variables | Array |
|---|---|---|
| Declaring 7 scores | int s1, s2, s3, s4, s5, s6, s7; | int scores[7]; |
| Assigning all values | s1=74; s2=91; ... (7 lines) | int scores[] = {74, 91, 55, 88, 100, 67, 83}; |
| Printing all values | 7 separate printf statements | One for loop with printf inside |
| Passing to a function | 7 separate parameters | One array parameter + one length int |
| Memory layout | Scattered anywhere in memory | Guaranteed contiguous (side-by-side) in memory |
| Access speed | Direct — O(1) | Direct — O(1) via index arithmetic |
| Adding an 8th value | Declare a new variable s8 | Change size: int scores[8] or add to initializer |
| Works well with loops | No — no index to increment | Yes — index aligns perfectly with loop counter |
🎯 Key Takeaways
- C arrays are zero-indexed — the first element is at index 0 and the last valid index is always size minus one. Accessing index equal to the size is undefined behavior and one of C's most common bugs.
- Array names decay to a pointer to the first element when passed to functions — which means sizeof inside a function gives you pointer size (8 bytes), not the array size. Always pass length as a separate parameter.
- sizeof(array) / sizeof(array[0]) is the correct way to calculate element count — but only in the same scope where the array was declared. Store the result in a named variable before your loop.
- Arrays guarantee contiguous memory — all elements sit side-by-side in RAM. This makes index access O(1) via simple arithmetic and makes arrays the fastest data structure for positional access in the entire C language.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Off-by-one error — accessing index equal to the array size — For an array int data[5], writing data[5] goes one past the end. C won't stop you and won't throw an error — it'll read whatever bytes happen to be in memory after the array. Your program might crash, produce wrong output, or appear to work fine until it doesn't. Fix: always use index < length (strictly less than) as your loop condition, never index <= length.
- ✕Mistake 2: Using sizeof inside a function to get array length — Writing int count = sizeof(numbers) / sizeof(numbers[0]) inside a function that received the array as a parameter gives you the pointer size (8 bytes on 64-bit systems), not the array size. The result will be 2 or 1 instead of the correct count. Fix: always pass the array length as a separate int parameter from the caller, where sizeof does give you the full array size.
- ✕Mistake 3: Forgetting to initialize array elements — Declaring int scores[5]; without an initializer list leaves all 5 slots holding garbage values (whatever bits happened to be in that memory). If you then loop through and print or use them, you'll get random large numbers. Fix: either use an initializer list int scores[5] = {0}; (which zeroes all elements), or explicitly assign every element before you read from it.
Interview Questions on This Topic
- QWhat is the difference between an array's name and a pointer in C, and in what situations does the array name not decay to a pointer?
- QIf I pass an array to a function and use sizeof inside that function to calculate the number of elements, what value will I get and why? How would you solve this problem correctly?
- QAn array int buffer[10] is declared but not initialized. A colleague says 'its elements are all zero by default.' Are they right? Under what specific condition would they be right, and why does this matter in production code?
Frequently Asked Questions
What is the size limit of an array in C?
There's no hard limit defined by the C language itself — the practical limit depends on where the array lives. Stack-allocated arrays (declared inside a function) are typically limited to a few megabytes before you risk a stack overflow. Heap-allocated arrays (using malloc) are limited by available RAM. For large data sets, always allocate on the heap.
Can you change the size of an array in C after declaring it?
No — standard C arrays have a fixed size set at declaration time and that size cannot change. If you need a resizable collection, you use dynamic memory allocation with malloc and realloc to manually manage a heap block that can grow. For beginners, just know that once you write int scores[5], that array will always have exactly 5 slots for its entire lifetime.
Why does C array indexing start at 0 instead of 1?
Because an index in C is actually an offset from the start of the array in memory. The first element is 0 steps away from the beginning — hence index 0. This makes the address calculation straightforward: element address = base_address + (index × element_size). Starting at 1 would mean the formula needed a constant subtraction every single time, adding cost to every array access. Starting at 0 keeps the math clean and fast.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.