Ask a programmer which languages start arrays at 0 and which start them at 1, and you’ll get a confident answer. Ask them why, and the conversation gets interesting fast.
Most developers assume the 0 vs. 1 question is settled — obviously 0-based is correct, because pointers, because C, because Dijkstra said so. But look at which languages chose 1-based indexing and a different pattern emerges. FORTRAN, MATLAB, R, Julia, Lua, Mathematica. Those aren’t amateur languages written by people who didn’t know better. They’re languages built specifically for numerical computing, statistics, and scientific work — and their communities chose 1 deliberately, for reasons that have nothing to do with ignorance of pointer arithmetic.
The debate isn’t over. It never really started as a debate — the two camps just built different things for different people, and the choice calcified into each language’s identity.
Where 0-Based Indexing Came From
The 0-based origin story is rooted in memory. In C, an array name is essentially a pointer to the first element. When you write array[i], the compiler translates that to *(array + i) — the value at the memory address i elements past the start.
If the first element is at offset 0, then array[0] means “zero steps from the start.” That’s the element right there at the pointer’s location. array[1] is one step forward. array[2] is two steps forward. The index is literally a byte offset divided by the element size.
This was formalized by Martin Richards in BCPL in 1966, inherited by C’s predecessor B, and then by C itself. Dennis Ritchie didn’t choose 0-based indexing because he thought it was the most human-friendly system. He chose it because it made pointer arithmetic clean and consistent. When you understand that p[0] means *(p + 0), there is no other logical choice.
That design decision propagated forward in a direct unbroken line: C++ (1985), Java (1995), Python (1991), JavaScript (1995), Ruby (1995), Rust (2010), Go (2009), Swift (2014), PHP (1994). Nearly every language designed for general-purpose programming inherited 0-based indexing from the C tradition, often without the pointer arithmetic rationale — Java, Python, and Rust don’t expose pointer arithmetic at all, but they still start at 0.
Where 1-Based Indexing Came From
The 1-based origin story is rooted in mathematics.
FORTRAN (1957) was designed for scientific computing on IBM mainframes. Its intended users were engineers and physicists who already knew how to work with vectors and matrices — mathematical objects that are 1-indexed by convention. A vector v has components v₁, v₂, v₃. A matrix A has elements A₁₁, A₁₂, A₁₃. That’s the notation in every linear algebra textbook from the 20th century.
COBOL (1959) followed with 1-based indexing for its tables — its users were business programmers working with records, accounts, and lists. Things you count starting from 1.
The mathematical tradition persisted in every language designed for scientific or numerical work: MATLAB (a matrix laboratory), Mathematica, R (a statistics language), Julia (explicitly designed for high-performance numerical computing). Lua.
These aren’t coincidences. The 1-based languages correlate almost exactly with scientific, mathematical, and domain-expert use cases. The community’s cognitive model shaped the language.
Dijkstra Weighed In (1982)
In 1982, Edsger Dijkstra wrote a short note titled “Why numbering should start at zero.” It’s become one of the most referenced documents in this debate.
Dijkstra’s argument was mathematical, not about pointers. Consider representing a sequence of natural numbers from a to b. You have four ways to write the bounds:
a ≤ i < ba < i ≤ ba ≤ i ≤ ba < i < b
Dijkstra argued that a ≤ i < b is the superior form for two reasons. First, the length of the range is b - a — simple subtraction, no adjustments. Second, when you start from zero, the lower bound of any range is simply 0, and the upper bound equals the length. The range [0, n) contains exactly n elements. You never have to choose between a ≤ i < b and a ≤ i ≤ b-1 or remember which convention a particular piece of code uses.
This eliminates a whole class of off-by-one errors in range expressions.
The argument is genuinely compelling, and it’s why for (int i = 0; i < n; i++) is correct C — and why for (int i = 1; i <= n; i++) is a bug that runs one iteration past the end of the array. The 0 and < go together. If you use 1-based iteration with < n, you process one fewer element than you should.
The Cognitive Mismatch
And yet.
Humans count from 1. When someone asks you to “name the third item in the list,” you don’t think “the item at index 2.” You think “the third one.” Children learn to count starting from 1. Languages are described in terms of first, second, third — not zeroth.
The off-by-one error — one of the most common bugs in 0-based languages — exists in large part because of the mismatch between 0-based array indices and the 1-based way humans naturally count. When you write a for loop over an array in C, Java, or Python, you have to mentally shift from human counting to machine counting, or vice versa. That shift is where bugs enter.
for (int i = 0; i < n; i++) is correct. But many programmers, especially beginners, write for (int i = 1; i <= n; i++) because that’s how they think about the data. The result is a loop that skips the first element and potentially accesses one past the end.
The 1-based languages don’t have this problem. When a MATLAB user writes A(1), they mean “the first element.” When a Lua developer iterates from 1 to #array, they’re thinking in the same terms as the data. The mismatch just doesn’t exist.
Lua: Built for Domain Experts, Not Computer Scientists
Lua was created in 1993 by Roberto Ierusalimschy and colleagues at PUC-Rio in Brazil. Its original purpose was to serve engineers and scientists at Petrobras, the Brazilian national oil company — people who were domain experts in petroleum engineering, not software engineering.
The language was designed from the start for non-programmer domain experts. And those users counted from 1, thought in 1-based terms, and would have found 0-based arrays alien and error-prone.
So Lua chose 1-based indexing, even though C was the obvious influence for everything else about the language. It was a deliberate decision rooted in who would be writing the code.
Today, Lua is embedded in dozens of environments — game engines (Roblox, World of Warcraft, many others), web servers (OpenResty/Nginx), Redis scripting, network equipment configurations. Most of its users are people who did not train as computer scientists. The 1-based design decision, made for petroleum engineers in 1993, is still serving that same population of domain experts decades later.
Julia: Choosing 1 from the Start, Eyes Open
Work on Julia began in 2009, when Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and Alan Edelman set out to create a free language that was both high-level and fast. The language was publicly announced in February 2012, by which point the 0-based convention was thoroughly established in mainstream programming. Its designers knew exactly what they were doing when they chose 1-based indexing.
Julia was built for numerical computing: linear algebra, numerical analysis, scientific simulation. Its intended users were mathematicians and scientists who think in terms of matrices and vectors — which are 1-indexed in mathematical notation. Choosing 0-based indexing would have created a constant translation burden between how users think about their data and how they express it in code.
Julia’s designers made the choice deliberately, acknowledging they were going against the trend.
But Julia also recognized that the 0-vs-1 question is really a special case of a more general question — and it went further than any other mainstream language in addressing that. Julia offers OffsetArrays.jl, a package that lets you define arrays with arbitrary starting indices. A temperature array might start at -50. A calendar array might start at 1. A spatial grid might start at 0. The package lets the index domain match the problem domain.
This is the most honest solution to the debate: don’t force either convention, and let the programmer choose the index that’s meaningful for their specific problem.
The Hidden Third Option: Arbitrary Bounds
Julia isn’t alone in offering this flexibility. It turns out the most historically capable languages — the ones designed by people who thought hardest about domain modeling — saw the 0-vs-1 question as a false binary from the start.
Modern Fortran lets you declare explicit lower bounds:
| |
Ada does the same:
| |
Pascal also supports arbitrary array bounds — a programmer declares the exact lower and upper index bounds that make sense for the problem, whether that’s array[1..12] for months, array[0..255] for byte values, or array[-7..7] for a centered range.
The indices mean something. A chess board has ranks 1 through 8 — that’s how chess notation works, and that’s how the array should be indexed. A 12-month calendar starts at 1 and ends at 12. A temperature range that goes from -50°C to 50°C could be indexed from -50 to 50.
The constraint that arrays must start at 0 or 1 is an artifact of specific language decisions, not an inherent property of arrays. Some languages just noticed this earlier than others.
The Same Access in Three Languages
Here is the same operation — create a small array and access its elements — in Python (0-based), Lua (1-based), and Fortran (flexible bounds):
| |
| |
| |
The Lua code reads exactly like how you’d describe the data in conversation: “the first fruit is apple, the second is banana, the third is cherry.” The Python code requires the reader to understand that index 0 means “first” — a convention that must be learned. The Fortran code goes further, letting the bounds be whatever makes sense for the domain.
The Camps at a Glance
| Convention | Languages | Root Justification |
|---|---|---|
| 0-based | C, C++, Java, Python, JavaScript, Ruby, Rust, Go, Swift, PHP | C pointer arithmetic; Dijkstra’s range argument |
| 1-based | FORTRAN, COBOL, Lua, MATLAB, Julia, Mathematica, R | Mathematical convention; human counting |
| Flexible bounds | Modern Fortran, Ada, Pascal, Julia (via OffsetArrays.jl) | Domain-meaningful indices |
The Argument Nobody Fully Wins
The 0-vs-1 debate has produced decades of strong opinions without a decisive resolution, and for good reason: both sides are genuinely right about something.
The 0-based camp is right that offset semantics are elegant, that Dijkstra’s range argument eliminates a class of off-by-one errors in correctly-written loops, and that for systems programming and general-purpose development, 0-based indexing is the natural model when you understand how memory works.
The 1-based camp is right that humans count from 1, that mathematical notation is 1-based, and that for scientists, statisticians, and domain experts who think in terms of first element and last element rather than offsets, 1-based indexing reduces cognitive load and makes code more directly correspond to the problem it models.
The real tell is in the communities those languages serve. Every major statistical language — R, SAS — is 1-based. Every major numerical computing environment — MATLAB, Mathematica, Julia — is 1-based. Every major scientific computing language — Fortran — is at least 1-based by default. These aren’t accidents or oversights. The scientists and mathematicians who built those tools understood exactly what convention they were using and why.
Meanwhile, every major operating system, web browser, database engine, and application framework is implemented in 0-based languages. The systems programmers and application developers who built those tools understood exactly what they were doing too.
The language’s choice of array indexing is a statement about who it was built for. It encodes an assumption about the cognitive model of the person writing the code — whether they think in terms of memory offsets or ordinal positions, whether they came from computer science or from a scientific domain.
That’s why neither side has won, and neither side will. The debate was never really about arrays. It was about whose mental model the language chose to honor.
Dig into the languages that made these choices: Fortran, Python, and C all have runnable examples on CodeArchaeology — with Docker so you can try them today.
Comments
Loading comments...
Leave a Comment