Scientyfic World

Why Does Array Index Start at 0, Not 1? The Real Reasons (With Code)

Array indexes start at 0 in most programming languages because the index is an offset: it counts how many elements come before the one you want, so the first element...

Share:

Get an AI summary of this article

blog banner of array start from zero with a vector image of a person with a

Array indexes start at 0 in most programming languages because the index is an offset: it counts how many elements come before the one you want, so the first element is 0 steps from the start. The convention comes from BCPL and C, where a[i] literally means “the address of a plus i elements”, and it survived because it makes ranges and loops simpler. Languages such as Lua, MATLAB, R, Julia and Fortran count from 1 instead.

Below, each part of that answer is shown with code I ran: the memory addresses of a C array, the machine code a compiler generates for 0-based and 1-based access, Dijkstra’s argument about ranges, and what happens in five languages when you get the index wrong.

What Zero-Based Indexing Means

In a zero-based (or 0-indexed) array, the first element has index 0, the second index 1, and the last index length − 1. In a one-based (1-indexed) array, the first element has index 1 and the last has index length.

langs = ["C", "Java", "Python", "JavaScript"]
print(langs[0])              # first element
print(langs[len(langs) - 1]) # last element
print(langs[-1])             # Python shortcut for the last element
for i in range(len(langs)):  # range(4) is 0, 1, 2, 3
    print(i, langs[i])
print(langs[0:2], langs[2:4])  # half-open slices split cleanly
try:
    langs[4]
except IndexError as e:
    print("IndexError:", e)
C
JavaScript
JavaScript
0 C
1 Java
2 Python
3 JavaScript
['C', 'Java'] ['Python', 'JavaScript']
IndexError: list index out of range

The easiest way to think about it is Edsger Dijkstra’s phrasing: an element’s index equals the number of elements before it. “C” has nothing before it, so its index is 0.

Reason 1: The Index Is an Offset From the Start

An array is stored as one block of memory. To find element i, the program takes the address of the first element and moves forward i × element size bytes. This C program prints where each element lives:

#include <stdio.h>

int main(void) {
    int a[4] = {10, 20, 30, 40};
    for (int i = 0; i < 4; i++) {
        printf("a[%d] = %d  at %p  (base + %zu bytes)\n",
               i, a[i], (void *)&a[i], (size_t)((char *)&a[i] - (char *)a));
    }
    printf("*(a + 2) = %d\n", *(a + 2));  /* what a[2] means */
    printf("2[a]     = %d\n", 2[a]);      /* legal, because a[i] is *(a + i) */
    return 0;
}
a[0] = 10  at 0x16f156670  (base + 0 bytes)
a[1] = 20  at 0x16f156674  (base + 4 bytes)
a[2] = 30  at 0x16f156678  (base + 8 bytes)
a[3] = 40  at 0x16f15667c  (base + 12 bytes)
*(a + 2) = 30
2[a]     = 30

Each int is 4 bytes, so element i sits exactly 4 × i bytes after the start, and element 0 sits at the start itself. The C standard defines a[i] as *(a + i): “go i elements past a and read what is there”. The odd-looking 2[a] compiles and prints 30 for the same reason, because *(2 + a) is the same as *(a + 2). With that definition, starting at 1 would mean the first element is at a + 1, one element past the start, which makes no sense.

Reason 2: History, From BCPL to C to Everything Else

Zero-based arrays didn’t start with C. In 2013, programmer Mike Hoye asked Martin Richards, who designed BCPL in the 1960s, why BCPL arrays start at 0. Richards explained that BCPL was a typeless language close to machine code: if v points to a block of words, v!i means the word at v + i, so “it is entirely natural for the first element of the array to have subscript zero”. He added that C copied this approach, using * and [ ] where BCPL used !.

C then shaped C++, Java, C#, JavaScript, Go, Rust and Python, which all kept 0-based arrays. Today, most programmers learn arrays in a 0-based language, so the convention reinforces itself.

Is 0-based indexing faster?

This is the most common explanation, and today it is mostly a myth. Starting at 1 means computing a + (i − 1), but compilers fold that −1 into the instruction. I compiled the same lookup both ways with Apple clang 21 at -O2:

int get_zero_based(const int *a, long i) { return a[i]; }
int get_one_based(const int *a, long i)  { return a[i - 1]; }
Zero-based a[i]One-based a[i − 1]
x86-64movl (%rdi,%rsi,4), %eaxmovl -4(%rdi,%rsi,4), %eax
ARM64 (Apple M2)ldr w0, [x0, x1, lsl #2]add x8, x0, x1, lsl #2
ldur w0, [x8, #-4]

On x86-64, both versions are a single instruction; the −4 byte offset is free. On ARM64, the one-based version needs one extra add. That cost is real but tiny, and loops often hide it completely. It mattered more on the hardware of the 1960s and 1970s, when BCPL and C were designed, than it does now.

Reason 3: Half-Open Ranges Are Simpler

The strongest modern argument comes from Edsger Dijkstra’s note “Why numbering should start at zero” (EWD831, August 1982). He compared four ways to write a range of integers and concluded that the best is lower ≤ i < upper, which includes the lower bound and excludes the upper one, because:

  • The length of the range is simply upper − lower.
  • Two adjacent ranges share a bound: 0 ≤ i < 2 and 2 ≤ i < 4.
  • An empty range is natural: 0 ≤ i < 0.

With that convention, a sequence of N elements gets the clean range 0 ≤ i < N if you start at 0, but the awkward 1 ≤ i < N + 1 if you start at 1. You can see the benefit in the Python example above: range(len(langs)) covers every index, and langs[0:2] and langs[2:4] split the list into two halves with no overlap and no gap. Dijkstra also noted that the Mesa language at Xerox PARC supported all four conventions, and its programmers found the other three “a constant source of clumsiness and mistakes”.

Which Languages Start Arrays at 1?

Starts at 0Starts at 1You choose the bounds
C, C++, C#, Java, JavaScript, TypeScript, Python, Go, Rust, Swift, Kotlin, PHP, RubyLua, MATLAB, R, Julia, Fortran (by default), COBOL, Smalltalk, MathematicaPascal, Ada, Fortran (declared bounds such as a(0:9))

One-based languages are mostly those built for mathematics and science, where matrices and vectors have always been numbered from 1. Lua shows how they behave:

local langs = {"C", "Java", "Python", "JavaScript"}
print(langs[1], langs[#langs])  -- Lua tables start at 1
print(langs[0])                 -- nil: there is no element 0
C	JavaScript
nil

Watch out for 1-based indexing outside “1-based languages”, too. In SQL, string positions start at 1, even when you call SQL from Python or JavaScript. In SQLite 3.54:

SELECT substr('scientyfic', 1, 3), substr('scientyfic', 0, 3), instr('scientyfic', 's');
-- sci | sc | 1

Passing 0, as you would in Python, silently returns one character fewer (sc) instead of raising an error.

Off-by-One Errors: The Price of Zero-Based Indexing

Because the last index is length − 1, not length, it is easy to go one step too far. Two quick examples:

  • An array with 15 elements, indexed from 0, has its last element at index 14.
  • For p = [12, 7, 9, 4, 20, 6], the last index is 5 and p[5] is 6.

What happens when you do go too far depends on the language:

LanguageReading one past the endWhat I saw
PythonRaises an exceptionIndexError: list index out of range
Java 21Raises an exceptionArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
JavaScriptReturns undefined, no errorundefined
LuaReturns nil, no errornil
CUndefined behaviour: reads whatever is in memorya[4] = -88325760 (garbage)

The C case is the dangerous one. This loop uses <= instead of <:

#include <stdio.h>
int main(void) {
    int a[4] = {10, 20, 30, 40};
    for (int i = 0; i <= 4; i++)   /* off-by-one: should be i < 4 */
        printf("a[%d] = %d\n", i, a[i]);
    return 0;
}

It compiled without a warning (with -Wall) and printed a garbage fifth value. Compiling with AddressSanitizer (-fsanitize=address) caught it immediately as a stack-buffer-overflow. Bugs like this are the source of many security vulnerabilities, which is one reason newer languages check bounds at runtime.

The habits that prevent off-by-one errors are simple: loop with i < length, not i <= length; prefer for item in items or for...of loops that don’t use indexes at all; and use built-ins for the last element (items[-1] in Python, items.at(-1) in JavaScript).

Zero-Based vs One-Based: Pros and Cons

Zero-basedOne-based
Matches memory offsetsYes: index = offsetNeeds a −1, usually free after compilation
Ranges0 ≤ i < N, length = N1 ≤ i ≤ N, closed range
Modular arithmetic (wrap-around)i % N works directlyNeeds (i − 1) % N + 1
Matches everyday countingNo: the “first” item is item 0Yes
Matches maths notationNoYes: matrices and vectors start at 1
Typical languagesC family, Python, JavaScript, Go, RustLua, MATLAB, R, Julia, Fortran

Neither is wrong. Zero-based indexing fits how memory and ranges work; one-based indexing fits how people and mathematicians count. What matters is knowing which one your language uses, especially when you move between, say, Python and SQL or R. If you are learning about arrays from the start, see arrays in C.

Conclusion

Arrays start at 0 because the index is an offset from the start of the array: a[i] means “move i elements past the beginning”, so the first element is at offset 0. The idea came from BCPL, C made it standard, and Dijkstra’s half-open ranges (0 ≤ i < N) explain why it remains convenient. The speed argument is mostly historical: a modern compiler makes 1-based access cost nothing or one extra instruction. The practical lesson is to remember that the last index is length − 1, and to let your language’s loops and bounds checks protect you from off-by-one errors.

FAQs

Why do array indexes start at 0 instead of 1?

Because the index is an offset: it counts how many elements come before the one you want. In C, a[i] means the element i positions after the start, so the first element is a[0]. C inherited this from BCPL, and most later languages copied C.

Does Python start at 0 or 1?

Python starts at 0 for lists, tuples and strings. The first element is items[0] and the last is items[len(items) – 1], or simply items[-1].

Does a string index start at 0?

In most languages, yes: Python, JavaScript, Java and C all start string indexes at 0. SQL is the common exception: SUBSTRING and substr positions start at 1.

What is a 1-indexed array?

An array whose first element has index 1 and whose last element has index equal to its length. Lua, MATLAB, R, Julia and Fortran use 1-based indexing by default.

If an array has 15 elements, what is the index of the last element?

With 0-based indexing, the last index is 15 – 1 = 14. With 1-based indexing, it is 15.

Is 0-based indexing faster than 1-based?

Barely, if at all, on modern hardware. Compilers fold the minus one into the memory instruction: on x86-64 both versions compile to one instruction, and on ARM64 the 1-based version needs one extra add.

Snehasish Konger
Developed @scientyficworld.org | Technical writer @Nected | Content Developer
Connect with Snehasish Konger

On This page

Take a Pause with Intervals

A Sunday letter on building, writing, and thinking deeper as a developer — short, honest, and worth your time.

Snehasish Konger profile photo

"Hey there — I'm Snehasish. Hope this post saved you some head-scratching time! I've spent years turning technical chaos into clarity, and I'm here to be your guide through the maze of modern tech. Stick around for more lightbulb moments — we're just getting started."

Related Posts