C / ARRAYS
Looping over arrays without running past the end
Write forward, reverse and pointer-based array loops in C whose stopping condition lands on the last real element instead of one step past it.
What you will learn
- Use i < n, never i <= n: with n elements the last valid index is n - 1.
- Compute the bound as sizeof arr / sizeof arr[0] while arr still has array type.
- Count down with for (size_t i = n; i-- > 0;) instead of i >= 0.
- Stop a lookahead loop at i + 1 < n, because n - 1 wraps when n is 0.
Understanding Looping over arrays without running past the end
An array of n elements has exactly n valid indices and they run from 0 to n - 1. Reading an index as "how many elements to step past the first" makes the arithmetic obvious: stepping past all n elements lands on arr[n], which is one element beyond the object. That is why the natural condition is i < n, a half-open range that includes 0 and excludes n. The body then runs exactly n times and the largest index it ever evaluates is n - 1.
The bound belongs in a variable, derived from the array rather than typed as a literal. Where the declaration is in scope, sizeof arr / sizeof arr[0] divides the total bytes of the object by the bytes of one element and yields the element count as a size_t, resolved at compile time. Storing that in n before the loop means the condition names the length instead of a number that quietly drifts the next time an initialiser is added or removed.
Indices are naturally size_t, which is unsigned, and that changes how a backwards loop must be written. An unsigned value is never less than zero, so for (size_t i = n - 1; i >= 0; i--) has a condition that can never become false: when i reaches 0 the decrement wraps it to the largest size_t and the loop walks off into unrelated memory. The idiom for (size_t i = n; i-- > 0; ) tests before decrementing, so the body sees n - 1 down to 0 and the wrap happens only after the loop has ended. The same wrap is why a loop that compares arr[i] with arr[i + 1] should stop at i + 1 < n rather than i < n - 1, since n - 1 with n == 0 is a huge number, not -1.
<stdio.h>
int main(void)
{
int temps[] = { 12, 15, 9, 21, 18 };
size_t n = sizeof temps / sizeof temps[0];
long sum = 0;
for (size_t i = 0; i < n; i++) {
printf("temps[%zu] = %d\n", i, temps[i]);
sum += temps[i];
}
printf("n = %zu, last valid index = %zu, sum = %ld\n", n, n - 1, sum);
return 0;
}
The valid indices are the half-open range 0 to n, so a correct loop stops at the first index that does not exist, which is what i < n expresses.
Worked examples
Counting down with an unsigned index
Shows the decrement-in-condition form that visits n - 1 down to 0 without ever testing an unsigned value for being negative.
<stdio.h>
int main(void)
{
const char *names[] = { "ada", "grace", "alan" };
size_t n = sizeof names / sizeof names[0];
for (size_t i = n; i-- > 0; ) {
printf("%zu: %s\n", i, names[i]);
}
printf("visited %zu names, highest index %zu\n", n, n - 1);
return 0;
}
Example explained
Line 1i starts at n, not n - 1, because the condition decrements it before the body runs.
Line 2i-- > 0 compares the current value and then subtracts one, so the body sees 2, 1, 0.
Line 3The final test happens with i == 0, fails, and the decrement wraps i to the largest size_t after the loop has already exited.
Line 4Written as for (size_t i = n - 1; i >= 0; i--) the condition would always hold and the wrapped index would read far outside names.
Walking to the one-past-the-end pointer
Uses a pointer as the cursor and the address just past the last element as the stopping mark.
<stdio.h>
int main(void)
{
double xs[4] = { 1.5, 2.5, 3.5, 4.5 };
double *end = xs + 4;
double total = 0.0;
for (double *p = xs; p != end; ++p) {
total += *p;
}
printf("elements %td, total %.1f\n", end - xs, total);
printf("end is %td element past xs[3]\n", end - (xs + 3));
return 0;
}
Example explained
Line 1p != end halts the walk exactly when p reaches the address after xs[3], so *p is only ever applied to a real element.
Line 2C allows xs + 4 to be formed and compared even though reading *end would be undefined; that guarantee is what makes this loop shape legal.
Line 3Subtracting two pointers into the same array counts elements, not bytes, so end - xs is 4 rather than 32.
Line 4Changing the test to p <= end would run the body once more with p == end and add whatever bytes follow the array.
Important notes
sizeof arr / sizeof arr[0] measures the object named in front of it, so it only gives the element count where the array declaration is visible; on a function parameter written int arr[] it divides one pointer's size by one element's size.
If the count comes from input and can legitimately be 0, i < n correctly runs the body zero times, while a do/while loop always executes once and reads arr[0] of an empty array.
Common mistakes
Writing i <= n: the extra pass evaluates arr[n], which is one element past the object, so the sum or printout picks up whatever bytes follow the array and the program may crash instead.
Counting down with for (size_t i = n - 1; i >= 0; i--): an unsigned index is never negative, so after index 0 it wraps to the largest size_t and the loop keeps reading memory that was never part of the array.
Hard-coding the bound as i < 5 next to an initialiser list: adding a sixth value silently leaves it unprocessed, and deleting one makes the loop read past the end.
Try it yourself
Change, predict, then run
Declare int a[] = {4, 8, 15, 16, 23, 42};, print every element with its index using a size_t loop bounded by sizeof, then print the same list backwards with the i-- > 0 form and confirm both directions produce six lines.
Open the C workspaceCheck your understanding
The body of a loop compares a[i] with a[i + 1]. The element count is held in a size_t called n, which may be 0 at runtime. Which condition is safe for every value of n?
- i < n - 1
- i + 1 < n
- i <= n - 1
- i < n
Show answer
i + 1 < n never subtracts, so it is false immediately when n is 0 or 1 and its last pass has i + 1 equal to n - 1. The tempting i < n - 1 looks equivalent, but with n == 0 the unsigned expression n - 1 wraps to the largest size_t and the loop scans an empty array for billions of iterations; i <= n - 1 wraps the same way and additionally lets the body read a[n].