Why do we count starting from zero? In computer science, we usually count starting from 0.  Is there any effective way to explain why, to new programmers who ask why?

I've read a bunch of different sources that list several reasons for 0-indexing.  However, they either seem unconvincing, or seem hard to explain to a new programmer who doesn't have much experience with computer science yet:


Zero-based indexing lets you write loop conditions as i &lt; n; with one-based indexing, we'd have to write i &lt; n+1, which saves one instruction.  This makes no sense to me; i &lt;= n seems like it would be fine.
In C, we can use *a to access the first element of array a.  This doesn't seem very relevant today for a new student who isn't programming in C.
With zero-based indexing, the expression a[i] compiles to an operation on memory address a + c*i where c is a constant representing the size of a single array element; with one-based indexing, it would need to compile to a + c*i - c.  This reason seems debatable today (couldn't a modern compiler often optimize this away?), but more importantly, I don't really want to be explaining memory addresses to a new programmer who doesn't understand memory layout and maybe doesn't even understand arrays yet.


Is there a better way to explain why to a new student who is just getting started with computer science?
None of the reasons you suggest really get to the heart of why we use zero-indexing in CS. Dijkstra's EWD831 explains why this convention works out the best. It comes down to the fact that we want to represent sequences of integers as half-open intervals that are inclusive on the start side.


  To denote the subsequence of natural numbers 2, 3, ..., 12 without the pernicious three dots, four conventions are open to us
  a) 2 ≤ i &lt; 13
  b) 1 &lt; i ≤ 12
  c) 2 ≤ i ≤ 12
  d) 1 &lt; i &lt; 13


To paraphrase Dijkstra:


(a) and (b) have the advantage that subtracting the bounds gives you the length, which is convenient because you don't need to remember to add or subtract 1.
(a) and (c) have the advantage that sequences starting with zero don't need a negative lower bound -- and negatives are no longer natural numbers.
(a) and (d) have the advantage that if you create an interval that starts with zero and shrink it down to zero-length, you don't need a negative for the upper bound.


Because of the above, we want to write all of our intervals as (a).

Once you accept that (a) is the correct way of specifying intervals, indexing an array of length N as [0, N) is much nicer than [1, N+1).



Two additional notes in favor of using (a) for intervals.

The first point above is important because half-open intervals nicely decompose into other half-open intervals. This makes implementing divide-and-conquer algorithms like merge sort significantly less error prone.

For example:


[4, 14) can be broken into the equal-sized intervals [4, 9) and [9, 14), and the middle index is computed as $9 = \frac{4 + 14}{2}$. This is quite clean and nice.
[4, 13], decomposes into [4, 8] and [9, 13], where you get the end of one sequence as $8 = \frac{4 + 13 + 1}{2} - 1$, and the beginning of the next sequence as $9 = \frac{4 + 13 + 1}{2}$. It's easy to forget to add or subtract 1 somewhere.


It's also a bit jarring to specify intervals with (b), because it feels unnatural to skip the first element when you're writing a loop.



As for how to explain the above to your students, I think that they just need to accept that breaking sequences of integers in half is something they'll do later on, and then a few examples will quickly point to [,) intervals and zero indexing as the most natural choice.