Why Python Uses 0-Based Indexing
Recently, someone on Twitter asked me why Python uses zero-based indexing (referred to as 0-based hereafter) and pointed me to an article on the subject (which was quite interesting). This brought back a lot of memories. The ABC language—one of Python's ancestors—used one-based indexing (referred to as 1-based), while C—another language that had a significant influence on Python—used 0-based indexing. Among the first few programming languages I learned (Algol, Fortran, Pascal), some used 1-based indexing, while others were variable. One of the reasons I decided to use 0-based indexing in Python was due to Python’s slice (subarray) syntax.
Let’s take a look at how slicing works. Perhaps the most common usage is "extracting the first n elements" or "extracting n elements starting from the i-th position" (the former being a special case where i equals the starting position). It would be very elegant if we could write this without needing ugly +1 or -1 adjustments.
Using 0-based indexing, Python’s half-open interval slicing and default bounds slicing become very elegant: a[:n] and a[i:i+n]. The full form of the first expression is actually a[0:n].
If we were using 1-based indexing, then a[:n] meaning "take the first n elements" wouldn’t work unless you used either closed-interval slicing or a two-parameter slicing syntax specifying both the start index and length. With 1-based indexing, half-open interval slicing becomes less elegant. Using closed intervals for slicing, to extract n elements starting from the i-th position, you'd have to write a[i:i+n-1]. In this context, it seems more appropriate to use a start:length format for slicing, which would allow you to write a[i:n]. In fact, this is what the ABC language did—it used a unique notation like a@i:n. (See http://homepages.cwi.nl/~steven/abc/qr.html#EXPRESSIONS.)
But does the start:length approach work well in other cases? To be honest, I don't fully recall, but I think I was captivated by the elegance of the half-open interval syntax. Especially when two slices are adjacent, and the end index of the first slice matches the start index of the second, it's too beautiful to give up. For example, if you want to split an array into three parts at points i and j, those parts will be a[:i], a[i:j], and a[j:].
This is why I chose to make Python use 0-based indexing.