<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/there-s-always-more-history-p1a6nitgv" -->

---
title: There&#x27;s Always More History | daily.dev
description: A deep dive into two classic programming mysteries: why Vim uses hjkl for navigation, and why JavaScript months are 0-indexed. The first layer of each...
canonical: https://daily.dev/posts/there-s-always-more-history-p1a6nitgv
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: There&#x27;s Always More History | daily.dev
og:description: A deep dive into two classic programming mysteries: why Vim uses hjkl for navigation, and why JavaScript months are 0-indexed. The first layer of each...
og:url: https://daily.dev/posts/there-s-always-more-history-p1a6nitgv
og:image: https://api.daily.dev/og/posts/p1a6nitGV.png
og:image:alt: There&#x27;s Always More History
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# There's Always More History

**[Lobsters](https://daily.dev/sources/lobsters)** · 8 min read · 0 upvotes · 0 comments

## Summary

A deep dive into two classic programming mysteries: why Vim uses hjkl for navigation, and why JavaScript months are 0-indexed. The first layer of each explanation is the commonly cited reason, but digging deeper reveals the true historical causes. Vim's hjkl keys trace back to the ADM-3A terminal's use of ASCII control characters (^H, ^J, ^K, ^L) for cursor movement. JavaScript's 0-indexed months trace back through Java and C to early Unix's ctime.c, where months were 0-indexed to enable efficient pointer arithmetic on memory-constrained PDP-7 hardware. The essay argues that most historical explanations are post-hoc rationalizations, and that the real reasons only emerge when you dig past the first layer.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.hillelwayne.com/post/always-more-history>

## Questions this post answers

### Why does Vim use hjkl instead of arrow keys for navigation?

Bill Joy developed vi on the ADM-3A terminal, which had no dedicated arrow keys but instead printed arrow symbols on the H, J, K, and L keys. That mapping existed because ctrl+H and ctrl+J were already standard ASCII control characters for backspace and linefeed (used as left and down on the ADM-3A), so K and L naturally became up and right, and vi simply followed that precedent.

_Digging into keyboard and editor history like this is easy to keep track of on daily.dev._

### Why are months 0-indexed in the JavaScript Date object?

JavaScript's Date API uses 0-11 for months to stay compatible with Java, which copied the convention from C. In early Unix's ctime.c, time fields were stored in an array and months were accessed via pointer arithmetic into a packed string of three-letter month abbreviations, so a 0-indexed month made that arithmetic simpler on memory-constrained 1970s hardware like the PDP-7.

_Understanding these quirks helps when debugging date logic, and daily.dev surfaces this kind of language-history context._

### Why is the day-of-month field 1-indexed in C's tm struct while day-of-year is 0-indexed?

Day of month (MDAY) starts at 1 because it was only ever used for direct display to users, so it was stored in the exact form shown. Day of year (YDAY) starts at 0 because it was only used internally for computation, specifically to calculate when daylight saving time starts in functions like sunday and localtime, so the 0-indexed form suited the arithmetic.

_Following these low-level C and Unix design decisions is simpler when they surface on daily.dev._

## Community take

How the wider developer community reacted, aggregated from 6 discussions and 47 comments across hackernews, lobsters (as of 2026-09-14).

**TL;DR:** Commenters broadly appreciate the essay's core insight — that historical explanations are often post-hoc rationalizations — and use it as a springboard for rich discussion about technical debt, protocol design, and the long shadow of early decisions. A minority push back on whether those early decisions were truly haphazard or simply reasonable given the constraints of the time.

**Sentiment:** 72% positive · 20% mixed · 8% skeptical

**The case for**

- The distinction between 'answers' and 'explanation' (post-hoc rationalization vs. genuine historical cause) is seen as the essay's most valuable and generalizable insight.
- The specific deep-dives into hjkl and 0-indexed months are praised as genuinely illuminating, not just trivia.
- The essay resonates strongly with developers who have personally witnessed early design decisions calcify into expensive long-term constraints.
- The 80-column/punch-card/US currency lineage is offered as another compelling example of the same phenomenon.

**The pushback**

- Some commenters argue the early decisions weren't 'haphazard' — they made perfect sense at the time and the longevity of the systems was simply unpredictable.
- The essay's framing risks encouraging hindsight bias; it's easy to call a decision short-sighted decades later.
- One commenter notes that in the Java Date case specifically, precedent was abundant enough that the mistake was foreseeable.

**By community**

- hackernews (positive): Engagement is light but appreciative, with commenters highlighting the 'answers vs. explanation' distinction as the essay's most profound takeaway and enjoying the specific historical examples.
- lobsters (mixed): A lively, substantive thread that praises the essay but quickly pivots to debating whether early design mistakes were truly avoidable, the skill of design thinking, and parallel examples like Python's GIL and the 80-column standard.

**Hottest debate:** Whether early design decisions that cause long-term pain were genuinely unforeseeable at the time or reflect an 'insufficient job of understanding the breadth of impact' — i.e., culpable short-sightedness vs. honest unknowability.

**Open questions**

- Are there good books or structured resources for developing the skill of thinking through protocol and system designs before committing to them?
- At what point does a design decision become 'locked in' by downstream adoption, and is there a practical way to identify that threshold earlier?

**Highlights**

> Whenever somebody at work tries to introduce technical debt at the initial design stage (with "it doesn't really matter" or "we'll change it later") I tell them that while implementation bugs are often fixed, protocol bugs typically get stratified as soon as anything at all uses the protocol. And then I try to remind them of the thirty or so actual examples, in our company, over the past ten years, that happen to come to mind off the top of my head, of somebody writing an internal service in a short-sighted way to save fifteen minutes of design time because "it's only a demo" and it costing us millions of dollars in engineer hours to replace or circumvent years later. The major problem with thinking through designs is not that well-thought-out designs are harder to implement (they tend to be simpler, rather than more complex) but that anybody who hasn't thought much about design concerns will have a hard time understanding the importance of particular elements, so explaining to junior developers or non-technical management (or "technical management" who haven't written a line of code in 10 years) why some vaguely-similar third-party library or some shallow hack is a bad idea takes a lot of time and effort. Thinking through designs is a specialized skill, and people who have learned this skill can (when properly motivated) think through designs pretty thoroughly pretty quickly & come up with high quality solutions that are straightforward to implement. The thing is, low quality solutions are traps: they hide complexity behind a veneer of simplicity, distract you with tangential concerns, and if you get caught by their hypnotic song then by the time you recognize their real cost, you have already begun to pay it.
> — [enkiv2 on lobsters · 11 points, 2 comments](https://lobste.rs/s/0v4l3f/there_s_always_more_history#c_nx6ici)

> Another random one that I once doubted, but now do not doubt, is the link between the standard 80-column line length/terminal/etc. and the size of circa-late-1880s US paper currency. (the 1890 US census was the first to use punched-card tabulating machines, developed by Herman Hollerith; the cards were stored in surplus currency boxes supplied by the Treasury, which capped the size of the cards; Hollerith's company later merged with others to form IBM; IBM improved the density of the punched cards -- still the same size -- to 80 columns; similar to the changes in the linked-list question, today whether to keep or abandon 80-character line lengths in code is usually debated using reasons which have nothing to do with the size of IBM punch cards or the historical cause of their size)
> — [ubernostrum on lobsters · 14 points, 1 comments](https://lobste.rs/s/0v4l3f/there_s_always_more_history#c_lslgw2)

> I think a better example might be the relationship between the 1990s fad for "interactive TV" setups where you'd have a box that you plugged in to the television set, and Python's GIL. Python predates Java (Python in 1991, Java in 1995). And it came out of the UNIX scripting-language world where if you needed to write a program that could multitask, you did it by forking new processes. Then Java came along, and one of its original targeted use cases was constrained systems like... those "interactive TV" boxes, which generally could not do true process-based multitasking. So Java went with threading, and threading became a hugely popular model. At the time, several things were true: * A huge part of Python's popularity was the relative ease with which it could wrap and interface to C. There wasn't an ecosystem of Python libraries, so much as there was an ecosystem of C libraries for which people had written wrappers to allow access from Python. * Neither the Python interpreter, nor most of that wrapper/C code, was thread-safe. * Most people who would *want* threading would be using it for I/O-bound tasks like writing network daemons. * Most people, even if they were writing multitasking code, still had only single-processor/single-core hardware on which to run it. So a decision was made to open up the ability to write threaded code in Python: there would be a lock which gated access to the interpreter and its C API, and only a thread -- and only one at a time -- which held the lock could execute Python bytecode or use the interpreter's C API. Thus, the Global Interpreter Lock. This was a perfectly reasonable decision then, and for quite some time into the future, and allowed people to keep using Python and all those handy wrapped C libraries as before, but with the addition of threading. And since your hardware was unlikely to be able to actually execute multiple threads simultaneously, and you'd almost certainly have lots of threads idling, doing things like waiting for data from a socket, it didn't really restrict you that much or impose too much performance penalty compared to the normal overhead of just using Python itself. But fast forward a few decades. Now we all have multiprocessor/multicore hardware, even in the phones we carry around in our pockets. And Python grew from a UNIX-y scripting language to something that's used in scientific computing with heavy CPU-bound number-crunching loads. And now we have endless wailing and gnashing of teeth about the GIL, and endless projects that try (and, so far, fail) to remove it, because making the whole thing thread-safe without the GIL today would still require a huge rewrite of all the legacy non-thread-safe code people wrote and wrapped in the past. (incidentally, the implementation of the GIL has evolved to try to reduce the overhead for some now-popular types of work, and number-crunching libraries like NumPy are careful to delineate -- there are macros for this -- which parts of their C code require access to the interpreter and thus the GIL, and which parts don't and can release the GIL while continuing to do work that doesn't involve the Python interpreter)
> — [ubernostrum on lobsters · 4 points, 1 comments](https://lobste.rs/s/0v4l3f/there_s_always_more_history#c_zny1k9)

> I don't think these examples are haphazard though; it all made perfect sense at the time, and it's hard to predict what will happen in the future (and if your prediction is wrong you're likely to end up with a much bigger mess). And you often don't really know what the longevity of a system will be; Bill Joy was just a grad student programming for the fun of it when he wrote `vi` and `csh` (and, in his own words when explaining some of csh's oddness, "I didn't really know what I was doing at the time"). I don't think he would have expected we'd still be looking back to his ADM-3A all these decades later.
> — [inactive-user on lobsters · 5 points, 1 comments](https://lobste.rs/s/0v4l3f/there_s_always_more_history#c_thye2j)

> Absolutely - the specific examples of "Why Vim Uses hjkl" and "Why JavaScript months start from 0" were very interesting in themselves. But the profound point was the generalisation: > We can always look further, peeling back more and more layers of the history. > But two layers is enough for this essay. With two layers, we can see a common pattern in studying history, the difference between answers and explanation. When asked why something is the way it is, most people will give a post-hoc rationalization. They’ll see the present and come up with reasons why it’s “better” for things to be that way. If you look a little into the past, you often see that “things are this way because they were this way”. And if you look deeper, you see the forces that lead to things becoming that way. > ... But it’s all worth the effort. Digging into the second layer teaches us much more about the context and reasons for why things are the way they are. And I can’t deny the puzzle aspect of it all, the joy in solving a mystery. Lost knowledge found again.
> — [isp on hackernews](https://news.ycombinator.com/item?id=36682962)

**Source threads**

- [hackernews](https://news.ycombinator.com/item?id=25349046) · 5 points · 0 comments
- [hackernews](https://news.ycombinator.com/item?id=26745977) · 4 points · 2 comments
- [hackernews](https://news.ycombinator.com/item?id=25354931) · 3 points · 0 comments
- [hackernews](https://news.ycombinator.com/item?id=25370424) · 2 points · 0 comments
- [lobsters](https://lobste.rs/s/0v4l3f/there_s_always_more_history) · 70 points · 27 comments
- [hackernews](https://news.ycombinator.com/item?id=36669810) · 68 points · 18 comments

## Similar posts on daily.dev

- [The Real Reason I Like niri and Vim — Nick Janetakis](https://daily.dev/posts/the-real-reason-i-like-niri-and-vim-nick-janetakis-qfk051lnp) · Nick Janetakis · 39 upvotes · 4 comments
- [Wolfram Function Repository](https://daily.dev/posts/wolfram-function-repository-debntyji0) · Hacker News · 1 upvotes · 0 comments
- [Modal editing is a weird historical contingency we have through sheer happenstance](https://daily.dev/posts/modal-editing-is-a-weird-historical-contingency-we-have-through-sheer-happenstance-5081dzalr) · Lobsters · 0 upvotes · 0 comments

---

Tags: [#javascript](https://daily.dev/tags/javascript), [#c](https://daily.dev/tags/c), [#vim](https://daily.dev/tags/vim)

[View this post on daily.dev](https://daily.dev/posts/there-s-always-more-history-p1a6nitgv)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"There's Always More History","url":"https://daily.dev/posts/there-s-always-more-history-p1a6nitgv","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/there-s-always-more-history-p1a6nitgv"},"datePublished":"2026-08-11T03:30:24.318Z","dateModified":"2026-09-14T07:53:58.575Z","description":"A deep dive into two classic programming mysteries: why Vim uses hjkl for navigation, and why JavaScript months are 0-indexed. The first layer of each...","image":"https://media.daily.dev/image/upload/s--P4t4XyoV--/f_auto/v1722860399/public/Placeholder%2001","thumbnailUrl":"https://media.daily.dev/image/upload/s--P4t4XyoV--/f_auto/v1722860399/public/Placeholder%2001","isAccessibleForFree":true,"articleSection":"Lobsters","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Lobsters","logo":"https://media.daily.dev/image/upload/s--tl8v_Fku--/f_auto,t_logo/v1698841318/logos/lobste.jpg","url":"https://daily.dev/sources/lobsters"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/there-s-always-more-history-p1a6nitgv","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"javascript,c,vim","timeRequired":"PT8M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Lobsters","item":"https://daily.dev/sources/lobsters"},{"@type":"ListItem","position":3,"name":"There's Always More History"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/there-s-always-more-history-p1a6nitgv#faq","mainEntity":[{"@type":"Question","name":"Why does Vim use hjkl instead of arrow keys for navigation?","acceptedAnswer":{"@type":"Answer","text":"Bill Joy developed vi on the ADM-3A terminal, which had no dedicated arrow keys but instead printed arrow symbols on the H, J, K, and L keys. That mapping existed because ctrl+H and ctrl+J were already standard ASCII control characters for backspace and linefeed (used as left and down on the ADM-3A), so K and L naturally became up and right, and vi simply followed that precedent. Digging into keyboard and editor history like this is easy to keep track of on daily.dev."}},{"@type":"Question","name":"Why are months 0-indexed in the JavaScript Date object?","acceptedAnswer":{"@type":"Answer","text":"JavaScript's Date API uses 0-11 for months to stay compatible with Java, which copied the convention from C. In early Unix's ctime.c, time fields were stored in an array and months were accessed via pointer arithmetic into a packed string of three-letter month abbreviations, so a 0-indexed month made that arithmetic simpler on memory-constrained 1970s hardware like the PDP-7. Understanding these quirks helps when debugging date logic, and daily.dev surfaces this kind of language-history context."}},{"@type":"Question","name":"Why is the day-of-month field 1-indexed in C's tm struct while day-of-year is 0-indexed?","acceptedAnswer":{"@type":"Answer","text":"Day of month (MDAY) starts at 1 because it was only ever used for direct display to users, so it was stored in the exact form shown. Day of year (YDAY) starts at 0 because it was only used internally for computation, specifically to calculate when daylight saving time starts in functions like sunday and localtime, so the 0-indexed form suited the arithmetic. Following these low-level C and Unix design decisions is simpler when they surface on daily.dev."}}]}
```

