0-based indexing is preferable because the mathematics of half-open intervals make composing ranges far simpler. It becomes a bit more obvious as soon as you do any kind of non-integer ranges, because you find that only integers have a reliable "subtract one" operation.<p>For instance, suppose you're doing a lot of date arithmetic, and you <i>might</i> include times. If I have a range:<p><pre><code> [2005-05-05, 2007-07-07)
</code></pre>
This unambiguously includes all times. If I compose it:<p><pre><code> [2005-05-05, 2007-07-07) [2007-07-07, 2008-08-08)
</code></pre>
If I shift all of them forward 10 days:<p><pre><code> [2005-05-15, 2007-07-17) [2007-07-17, 2008-08-18)
</code></pre>
Every possible time within the new range is accounted for. Transformations on the ranges are obviously correct. If we can't agree on whether time resolves to seconds, milliseconds or nanoseconds, it doesn't matter.<p>That's because you can find the beginning and end of a range without knowing how to subtract by one. If you later decide to add times, it will just work because, conceptually, you already handle every possible value in the range.<p>If you want to construct a series of ranges of floats, it's a bad idea to do [3.457, 6.799999], [6.8, 12.47699999]. If you use half-open intervals, it just works, without mucking with the end of the range.<p>This is the reason that "indices are offsets" is powerful: it's not circular reasoning, but a simplification because we're removing an unnecessary primitive (subtracct 1) that only reliably works with integers.<p>And to bring this back to integer offsets, even when you're working strictly with integers, you may want to conceptually work with rational numbers.<p>When you're working out your math for buffered IO, you need to map byte indices to chunk indices. You need to assign every byte written to a chunk.<p>In a 0-based world, your chunk offset is simply your byte offset / chunk length. Because there is no distinction between indices and offsets, it's a clean mapping between one offset scheme and the other.<p>It means you can work out on paper when a new chunk should be started, and then translate that into integer arithmetic. And your code directly reflects the math, rather than having to stick "- 1" and "+ 1" in here and there.