Skip to content

Programming Languages Are Authoring Tools for Platforms.

That's what I think.

Makonea
·Jul 20, 2026·95 min

Introduction...

When planning a new project and deciding which language to use, people often fall into one of two illusions. They either venerate an "academic lineage" such as functional paradigms or the purity of a type system, or they blindly follow "community trends" swept up by GitHub star counts and the buzz in programmer communities. It is a naive assumption that elegant mathematical proofs or trendy syntax will guarantee a product's survival, though in some ways it is understandable. People want the language they staked their career on learning, and its Worldview / Paradigm, to stand the test of time. Honestly, if C# and TypeScript started losing market share tomorrow, I would be plastering every programming forum with arguments for how superior they are.

Regardless, every program's execution ultimately reduces to instructions and state transitions carried out by a machine. When an interpreter reads source code or bytecode step by step, the original program may never be fully translated into machine code. Even so, if we're only concerned with computability, high-level languages are not strictly necessary. The same computations can be expressed in machine code or assembly alone.

Yet actual history unfolded as we know it. People did not write every program in assembly and C alone. A vast number of high-level languages and runtime environments emerged to address different problems and Platforms.

Each time new hardware appeared, operating systems arose, and browsers and smartphones became widespread, the languages and tools for working with those surfaces followed close behind. That is because the more expensive problem shifted from whether a machine can compute something to how many programs people can build on top of that machine.

IBM's ceding of PC Platform leadership to Microsoft can be read through the same lens. IBM created the hardware standard known as the PC, but the application software and developer ecosystem built on top of it became bound to MS-DOS and the Windows API rather than to IBM's machines. As compatible PCs proliferated, hardware became a commodity, and Microsoft, which controlled the interface through which developers wrote their programs, became the Platform's true owner.1 An empty Platform is not yet a product. Only when games, productivity applications, and content accumulate on top of it does it become a world where users stay. A language is the tool that authors that world.

And the moment a programmer takes hold of that tool and imposes logic and rules on the empty space, they become something more than a mere machine operator.

The computer programmer, however, is a creator of universes for which he alone is the lawgiver.

This is a line left by Joseph Weizenbaum, the creator of ELIZA, in Computer Power and Human Reason.

Yet the fact that programmers can build worlds with a language does not, by itself, explain that language's success. A highly expressive language can remain confined to a small research community, while a widely criticized language can encounter a massive Runtime environment and become an industry standard.

Discussions of programming languages typically begin from academic classifications such as type systems, semantics, paradigms, and implementation strategies. These classifications matter, but I believe they alone cannot explain the actual history of languages. My view is that the job market perspective is a better answer to why people learn a programming language. It was that thinking, through the lens of the job market and of markets more broadly, that prompted me to write this piece.

There is no shortage of writing that traces programming's academic lineage, so anything I add along those lines would have little competitive edge. Instead, I want to offer a different perspective.

In my view, reading the history of languages requires distinguishing at least three questions.

Loading animated diagram...

Why was it created? Why did developers adopt it? Why did it remain dominant for so long? These are the most fundamental questions, in my opinion. A language built for research can succeed by meeting a massive industrial Platform, while a language built for a specific Platform can escape beyond that Platform's boundaries. Trying to explain all of the history that follows from a language's original motivation alone quickly runs into trouble.

Platforms and Complementary Goods

The value of an operating system, a browser, a game console, or a smartphone grows alongside the programs that run on it. What users encounter every day is not the system's kernel or its design elegance, but the end products: productivity applications and games. A game console with no games to play is nothing more than an expensive piece of living room furniture.

In economics, goods that maximize their value when consumed together are called Complementary Goods. Platforms and applications have exactly this kind of complementary relationship.

If infrastructure is a vast empty stage, then applications and content are the actors and scripts that fill it. No matter how state-of-the-art a theater's sound and lighting systems are, audiences will not come if there is nothing to perform on that stage. In other words, a platform's real commercial value and market dominance are determined not by its own computational power, but wholly and dependently by the total volume of Complementary Goods (killer applications) continuously produced within its ecosystem.

Conceptually, this can be summarized as follows.

Loading animated diagram...

For platform operators, development tools are analogous to factory equipment. It does not matter if selling compilers is not the core revenue source. When developers can build programs more cheaply and quickly, the number of Complementary Goods on the platform increases, and the platform itself becomes more valuable. The strategy Joel Spolsky described as "commoditize your complements" applies equally to languages and SDKs.2

So the question of whether "high-level languages were created to sell compilers" is, by my measure, a somewhat one-dimensional framing. Corporate revenue has more often come from computers, operating systems, runtimes, browsers, and smartphones. A language is what gives people a reason to keep using those products.

The Costs That Languages Reduce

The costs a language reduces go well beyond the amount of typing it saves.

Loading animated diagram...

Saying "you can implement it in assembly" is an answer about Turing completeness, that is, about computational expressiveness. But whether it can be built within a business schedule, whether a different team can modify it years later, and whether it can be ported to the next machine are entirely different questions, ones about development cost and practical utility.

Turing-complete languages can, in principle, express the same computations, but the problem is that the cost of writing, verifying, and maintaining those computations is not the same across languages. How, then, should we view differences in language design? Generally, those differences show up not so much in what can be computed, but in how the costs and responsibilities are distributed among humans, compilers, and runtimes.

What question gets asked most repeatedly in the real world? Whether something is financially viable. That is why the shift from the possibility of computation to the distribution of its costs is so important.

Viewed from this perspective, the history of programming languages can be read as a succession of responses to whatever resource was most expensive in each era's systems. When hardware was scarce and costly, execution efficiency was the dominant constraint. But as software grew larger and development organizations expanded, the human costs of writing, verifying, and maintaining code became equally significant.

High-level languages transfer some of the work once shouldered by human memory and attention to compilers, type systems, garbage collectors, and libraries. In that sense, a programming language is simultaneously a notation for issuing instructions to a machine and an interface for allocating cognitive cost to individuals and development cost to organizations.

(Think of this as the Sapir-Whorf hypothesis applied to programming.)

If C Can Do It, Why Use Python?

Alan Perlis wrote the following as the nineteenth epigram in Epigrams on Programming in 1982.3

(Alan Perlis)

"A language that doesn't affect the way you think about programming, is not worth knowing."

This could also be rendered as a language that changes your values, and the meaning still holds. Here, though, "values" refers less to political convictions and more to the habits through which you perceive a program: what you treat as a single value, what operations you give names to, where you place state and failure, and what combinations you can express concisely. A language determines all of these things. At its core, a language is a bundle of trade-offs and a collection of subsets. (Here, subset refers to the 'permitted space of constraints' that language designers intentionally carved out from the infinite freedom a machine possesses.)

In other words, what domain you choose to focus on and what you set out to do is shaped by your choice of language.

Applying this to Python raises a natural question. CPython is implemented in C, and performance-critical extension modules are often written in C or C++ as well. If C is doing the execution anyway, why not write everything in C from the start?

Looking only at the underlying materials, that argument seems reasonable, but the worlds each author operates in are entirely different. Someone calling a library directly from C must contend with pointers, buffer lengths, ownership, error codes, type conversions, and linking. A Python extension module transforms that work into functions and objects you can reach via import. Even the official C extension API is described as a boundary for creating new object types or exposing C libraries and system calls for use from Python.4

For instance, even when the inner loops of numerical computation are handled by C, C++, Fortran, and BLAS, the user passes arrays around as single values, composes functions, and inspects results directly in a notebook. The implementation world of traversing memory addresses and the authoring world of manipulating matrices, dataframes, and models coexist within the same program. Python is less a replacement for C than a means of working with already-existing native code at a different level of granularity.

The notion of a language's worldview (Paradigm) can be summarized as follows.

What the language determines

Surface closer to C

Surface closer to Python

Basic unit of work

Addresses, buffers, structs, function calls

Objects, iterables, modules, callables

Resources and Lifetimes

Explicit allocation/deallocation, ownership conventions

Object lifetimes and context managers

Failure propagation

Return values, errno, ad-hoc conventions

Exceptions and tracebacks

Composition model

Headers, ABI, linking, function pointers

import, protocols, higher-order functions

Exploration style

Build-then-run and debuggers

REPL, introspection, notebooks

What the language determines

Basic unit of work

Surface closer to C

Addresses, buffers, structs, function calls

Surface closer to Python

Objects, iterables, modules, callables

What the language determines

Resources and Lifetimes

Surface closer to C

Explicit allocation/deallocation, ownership conventions

Surface closer to Python

Object lifetimes and context managers

What the language determines

Failure propagation

Surface closer to C

Return values, errno, ad-hoc conventions

Surface closer to Python

Exceptions and tracebacks

What the language determines

Composition model

Surface closer to C

Headers, ABI, linking, function pointers

Surface closer to Python

import, protocols, higher-order functions

What the language determines

Exploration style

Surface closer to C

Build-then-run and debuggers

Surface closer to Python

REPL, introspection, notebooks

In most programming communities, C is revered as "real" programming precisely because it is older and closer to the machine. But programmers have finite cognitive resources, and they must always make choices about how to allocate those resources. (Framing it this way risks sounding like Silicon Valley-style Taylorism, but on this particular point I find myself in agreement with the Taylorist position.)

If you are investing in numerical computation, it makes sense to focus your attention on low-level control, issuing instructions directly to the machine. On the other hand, if your cognitive energy needs to go toward modeling data flows and business-domain rules, then you should be willing to delegate the mechanical details of memory management and thread synchronization to the black boxes of a Garbage Collector and Runtime.

No one can do everything. Choosing a programming language is fundamentally a choice about how to be effective within the limits of your own cognitive resources.

Even within the same system, the inner layers should stay close to the machine while the outer layers should be easy for people to modify frequently. The division of labor in which C handles computational cost and memory layout while Python handles experimentation sequences and assembly has survived so long precisely for this reason. The fact that C sits beneath Python tells us less about Python's redundancy and more about how the two languages handle different rates of change.

Of course, everyone knows there is a tax at the boundary. Object conversion and memory copying, ABI and packaging, the GIL, translating errors raised in C into Python exceptions, and the difficulty of tracking down native crashes when all you have is a Python traceback—these costs remain. Abstraction does not eliminate implementation costs; instead, it concentrates them among a small number of extension-module authors, freeing a far larger population of users to write code in the vocabulary of their problem domain.

From this perspective, learning a new language is not simply a matter of swapping out syntactic symbols. LISP taught us to treat code as data; Prolog foregrounded relations and search rather than procedures; Rust brought ownership and borrowing into the type-checking system. Python, too, elevated iterable objects, protocols, dynamic composition, and interactive exploration into everyday units of work. If a new language lets you express the same ideas as the old one with nothing more than a semicolon change, Perlis's criterion gives you little reason to bother learning it. Conversely, even if the implementation underneath still runs on the same C and machine code, if the problems the author perceives and the sentences available to express them have changed, it is already a different Authoring Tool.

That is why the choice of language should be driven by what you want to do, and when you choose a language, you are entering that language's Worldview.

Rereading language history through Platform economics

Here I take Fortran as the starting point. There were important precursors—Plankalkül, Short Code, A-0, Autocode—so this is not a race to crown the "first high-level language." Programmers, the moment any language discussion comes up, love to pipe in with "Well, actually, the true first was OOO" and start reciting the genealogy of dead languages, but I am not here to pick a thoroughbred for the Kentucky Derby. (If bloodlines are your thing, go play Uma Musume. Uma Musume is great.) I will leave the task of setting ancestral offerings before languages that died in the 1950s to the Wikipedia editors. The only thing I care about is this: how did these languages lock developers in and become the power behind a Platform? The tangents can wait—starting from the 1957 Fortran report gives us a relatively clear view of the moment when a language, a Compiler, commercial computers, and a user market all converge within a single document.

One more thing worth noting: the year a language was first implemented and the publication year of the canonical document we read today do not always match. The HOPL papers on Smalltalk, C, C++, and Prolog are not introductions written at the time of birth but retrospectives that their designers composed years later. The Fortran report, the COBOL initial specification, the ALGOL 60 report, and the Dartmouth BASIC manual, by contrast, are closer to documents of the era. This is precisely the headache we run into whenever we try to compile a history of programming. Languages born in universities and committees arrive with a birth certificate and a pedigree (a specification) already in hand. Languages like C or C++, on the other hand, first swallowed the world whole from a basement, and only a decade or so later, when they had run out of things to conquer, showed up at conferences in neckties to write self-flattering retrospectives.

The difference between a language whose pedigree was written first, and one that made its money first and bought a pedigree afterward.

Period

Language

Initial environment

Character of the document examined here

1957

Fortran

IBM 704

Contemporaneous reports announcing compilers and languages

1960

COBOL

Heterogeneous data processing for government and enterprise

CODASYL initial specification

1960

LISP

MIT AI Group, IBM 704

McCarthy's original paper

1960

ALGOL 60

International algorithmic notation

Contemporaneous language report

1964

BASIC

Dartmouth Time-Sharing System

1964 original official manual

1962–1967

Simula I · Simula 67

Discrete event simulation

1966 Simula I introductory paper and subsequent Simula 67 lineage

Post-1972

Smalltalk

Personal computing at Xerox PARC

1993 designer retrospective

1970s

C · ML · Prolog

Unix · LCF · Natural language processing

Mix of contemporary papers and HOPL retrospectives

Post-1979

C++

C system ecosystem

1993 designer retrospective

Post-1989

Python

Unix/C extension and rapid scripting

First paper 1991, specification 1995

Post-1995

Java · JavaScript

JVM · web browser

Early white papers and later HOPL retrospectives

Post-2000

C#

.NET and CLI

First ECMA standard 2001

2015 and Beyond

Rust's Platform Adoption

Android, Windows, and Linux

Official Platform Documentation and Security Policy Reports

Period

1957

Language

Fortran

Initial environment

IBM 704

Character of the document examined here

Contemporaneous reports announcing compilers and languages

Period

1960

Language

COBOL

Initial environment

Heterogeneous data processing for government and enterprise

Character of the document examined here

CODASYL initial specification

Period

1960

Language

LISP

Initial environment

MIT AI Group, IBM 704

Character of the document examined here

McCarthy's original paper

Period

1960

Language

ALGOL 60

Initial environment

International algorithmic notation

Character of the document examined here

Contemporaneous language report

Period

1964

Language

BASIC

Initial environment

Dartmouth Time-Sharing System

Character of the document examined here

1964 original official manual

Period

1962–1967

Language

Simula I · Simula 67

Initial environment

Discrete event simulation

Character of the document examined here

1966 Simula I introductory paper and subsequent Simula 67 lineage

Period

Post-1972

Language

Smalltalk

Initial environment

Personal computing at Xerox PARC

Character of the document examined here

1993 designer retrospective

Period

1970s

Language

C · ML · Prolog

Initial environment

Unix · LCF · Natural language processing

Character of the document examined here

Mix of contemporary papers and HOPL retrospectives

Period

Post-1979

Language

C++

Initial environment

C system ecosystem

Character of the document examined here

1993 designer retrospective

Period

Post-1989

Language

Python

Initial environment

Unix/C extension and rapid scripting

Character of the document examined here

First paper 1991, specification 1995

Period

Post-1995

Language

Java · JavaScript

Initial environment

JVM · web browser

Character of the document examined here

Early white papers and later HOPL retrospectives

Period

Post-2000

Language

C#

Initial environment

.NET and CLI

Character of the document examined here

First ECMA standard 2001

Period

2015 and Beyond

Language

Rust's Platform Adoption

Initial environment

Android, Windows, and Linux

Character of the document examined here

Official Platform Documentation and Security Policy Reports

He estimated that it might have taken three days to code this job by hand, plus an unknown time to debug it, and that no appreciable increase in speed of execution would have been achieved thereby.

1957: Fortran — Let's Sell a Compiler!

The paper Backus and his IBM team published in 1957 was titled The FORTRAN Automatic Coding System. Its central subject was not just the language's grammar, but an automatic coding system that actually ran on the IBM 704. What users feared at the time was not so much the unfamiliarity of high-level notation, but the possibility that the compiler might fail to produce results as fast as code hand-crafted by a skilled programmer. If you spent expensive machine time to save programming effort and ended up with slow object code, the deal fell apart.

The early Fortran team therefore had to reduce writing time while still generating efficient machine code. The abstraction cost of the language had to be repaid by compiler optimization. Reading that report makes clear that the first industrial case for a high-level language was not made on "readable code" alone. The argument was that it would reduce programmer time without wasting the computational power of the IBM 704.5

1960: Three Different Answers in the Same Year

Placing COBOL, LISP, and ALGOL 60 side by side, all from 1960, reveals that each gave a different answer to the question of why high-level languages were necessary.

The very title of COBOL's initial specification already contains Common Business Oriented Language. It was a shared language intended to let programs handling organizational records — banking, insurance, payroll, inventory — be ported across computers from different manufacturers. Rather than a specific machine's instruction set, records, reports, and business procedures took center stage. COBOL therefore had to take the form of "English sentences" rather than mathematical formulas, not as a convenience for developers, but because stakeholders wanted the code itself to serve as a "business manual" that managers and accountants, not just programmers, could read and verify directly. Moreover, this was not the product specification of a single company; it was the political and administrative outcome of a negotiated compromise reached by the CODASYL committee, which brought together government (the Department of Defense) and industry.6

Where Fortran translated scientific formulas into machine instructions, COBOL transplanted an organization's ledgers and bureaucratic procedures wholesale. In practice, COBOL was used to build the circulatory system of capitalism itself, from the payment networks of global banks to the inventory processing of vast retail chains.

From this point on, the meaning of "platform" shifted away from "a single machine made of metal" toward "an organization's massive data-processing infrastructure" as a whole. Once a language could paper over the syntactic differences among hardware vendors, businesses were freed from Vendor Lock-in. The hardware would be discarded when it reached end of life, but organizations believed that the enormous Human Capital they had poured into writing COBOL programs and training staff would migrate intact to the next generation of machines, allowing them to recoup that investment (ROI) indefinitely. And one could argue that the investment was, in fact, preserved perfectly.

So perfectly preserved, in fact, that a strange comedy played out in which global banks in the 2020s had to court retired septuagenarians, pay them hundreds of dollars an hour, and beg them to keep the systems running. The code had achieved immortality, but someone forgot that Human Capital grows old.

In that same year, McCarthy's LISP paper addressed an entirely different problem. The MIT AI group wanted to represent formalized declarations and instructions on the IBM 704 and to run reasoning experiments on those representations. Symbols and lists, rather than arrays of numbers, became the primary data, and the paper unified recursive functions, conditional expressions, cons, car, cdr, and eval into a single computational system.7

Viewing LISP's platform as simply the IBM 704 tells only half the story. The physical machine was indeed the 704, but what the researchers were trying to create was not a numerical computation program; it was a new computational world centered on symbol manipulation. The key point is that because code and data shared the same list representation, a path opened for treating the language itself as an object within the language. The fact that various Lisp machines and environments like Emacs later bundled language, Runtime, and editor into a single package was not an accidental change of direction.

The ALGOL 60 report strictly separated the reference, hardware, and publication representations of a language rather than tying it to any particular vendor's Compiler. Before ALGOL, a working compiler essentially defined a language's identity; after ALGOL, the Specification became the language's authoritative body.8

In the commercial market, ALGOL never dominated the way Fortran or COBOL did, churning out invoices and pay stubs. Yet by hardcoding structural constraints, such as block structure, Lexical Scope, and recursive calls, that would become the skeleton of modern languages, ALGOL reigned as a 'conceptual Platform' whose DNA carried forward into every subsequent language, from C and Pascal to Java.

1964: BASIC Was Designed Alongside Response Time

The 1964 Dartmouth BASIC manual stated in its subtitle that it was an "elementary algebraic language designed for the Dartmouth Time Sharing System." Studying BASIC in isolation from that context makes it easy to miss why its syntax turned out the way it did. Standing alongside it was the time-sharing system (DTSS), in which students typed programs at a terminal and received results within a short turnaround time.9

If Fortran reduced the cost of routing work through professional programmers, BASIC let students with no computer science background use a computing device directly on their own. Instead of submitting a batch job and waiting, they could edit interactively and rerun immediately. The Authoring Tool here was the total experience of the BASIC syntax, the time-sharing operating system, and the terminal taken together.

The company that understood the disruptive power of this "immediate authoring experience" most precisely and weaponized it as a business was the early Microsoft. In 1975, Bill Gates and Paul Allen ported a BASIC interpreter to the Altair 8800, a machine that previously only accepted switch inputs, and founded their company on that basis. To them, BASIC was not merely a language; it was the most powerful Abstraction Layer available for papering over fragmented hardware.

Microsoft subsequently burned its own BASIC into the ROM of countless early PCs, including machines from IBM, Apple, and Commodore. The prompt that appeared the instant you powered on effectively stood in for an OS, and hardware manufacturers found themselves locked into the language ecosystem Microsoft had defined. The philosophy of "combining language with an interactive environment" that originated at Dartmouth carried forward into Visual Basic, which let anyone in a Windows environment build applications through drag-and-drop, completing the democratization of software productivity. Of course, beneath the shade of that "democracy," it also left more than a few young factory contractors in Ulsan staring at VB6 code written in 1998, no comments, variable names like a, b, and c, weeping over the existential question of where on earth to even begin making changes.

In any case, this combination reappeared later on personal computers. A machine with a BASIC interpreter in ROM showed the programming surface the moment you powered it on. Before installing a separate SDK, a user could already become an author on that machine.

1962–1972: Beginning to Author Worlds, Not Just Programs

The name Simula first appears in documentation in 1962. The paper that Dahl and Nygaard published in 1966 introduces Simula I, an extension of ALGOL 60 designed to describe discrete-event systems concisely. It would be wrong to retroactively read all of Simula 67's concepts into that paper. Even so, the direction of organizing the activities and events within a system in terms closer to real-world components is already clear. When Simula 67 formalized class, object, and inheritance, a program became not only a computational procedure but also a model of a world whose components interact with one another.10

Smalltalk pushed this idea across an entire personal computing environment. Alan Kay's HOPL retrospective traces Smalltalk's origins to time-sharing computers, graphical displays, pointing devices, and 1960s research on human-computer symbiosis. The 1972 Smalltalk was not just a single syntax; it was a medium in which objects, messages, a display, a browser, a debugger, and a live image were all connected.11

Today it is easy to think of a language and an IDE as separate products, but in Smalltalk the environment for writing code, running it, inspecting its internals, and modifying it all lived inside one system. Users did not merely produce finished programs; they reshaped the live computational medium itself. This is probably one of the cases where the statement "a programming language is an Authoring Tool for a Platform" holds most literally.

1970s: Languages Embedded Inside Other Tools

ML was not, from the start, a product aimed at the independent general-purpose language market. It was used as the metalanguage for writing theorem-proving tactics in Edinburgh LCF. LCF preserved the logical soundness of proof objects through a small trusted kernel and a theorem abstraction that could not be arbitrarily constructed from outside. ML's polymorphic type inference and abstract types caught common type errors in tactic code early and restricted access to internal representations. Milner's 1978 paper explains that this type discipline and Algorithm W were implemented in ML, though Algorithm W itself does not guarantee the truth of a proof.12

In this case, the host Platform can be said to be the theorem prover rather than an operating system. ML was a safe authoring language for extending LCF, and within it, verification automation strategies were constructed. That internal language of a research tool later branched out into Standard ML, OCaml, and numerous type systems. This episode, in fact, suggests that the history of programming languages should be understood not only as a narrative of "capturing a dominant Platform" but also as a history of by-products, languages born to solve a specific domain problem that then took on a life of their own.

Prolog did not begin with a declaration to create a new language either. According to Colmerauer and Roussel's HOPL retrospective, the starting point was a French natural-language processing project, and after a preliminary version in late 1971, a more settled system emerged by late 1972. The approach of having an interpreter search for answers once logical relations were described grew out of the practical need to write parsers and inference rules.13

LISP, ML, and Prolog are difficult to explain through any single hardware sales strategy. Instead, the working environments of AI research, theorem proving, and natural-language processing demanded these languages, and the languages in turn expanded the range of experiments possible within those research environments.

In other words, authoring tools have been shaped by the desire to control a particular Platform (domain, machine, or business).

After the 1970s: the empire of compatibility built by C and Unix

The relationship between C and Unix rather explicitly illustrates the view that a language is an authoring tool for its Platform. Neither side unilaterally made the other successful; each created the conditions for the other to spread.

After Multics ended, returning to smaller systems

In the late 1960s, Bell Labs was pulling out of Multics, the project it had been building with MIT and General Electric. According to Dennis Ritchie's retrospective, the researchers had begun to feel that by the time Multics reached a usable form, it would be too late and far too costly. The ideas learned from Multics, including processes, hierarchical file systems, user-level command interpreters, and device access, nonetheless carried forward. A small group centered on Ken Thompson set out to recreate, on a much smaller machine, an environment they could control themselves.14

This was not yet a period when Unix existed as an official project at the company. Thompson, Ritchie, J. F. Ossanna, and others made multiple requests throughout 1969 to purchase a PDP-10 or Sigma 7, but none were granted.

Around that time, Thompson ported his solar-system game Space Travel, which had been running in GE-635's expensive interactive batch environment, to the largely unused PDP-7. According to Ritchie's separate account, the game came before Unix, and the work gave Thompson the opportunity to become familiar with the PDP-7 and use it freely. Space Travel was later often retold as a founding myth of Unix, but the role Ritchie attributed to it is more limited: the game brought Thompson to the PDP-7, and on that machine he was able to begin the file system and operating system experiments he had already been contemplating.14

The first system on the PDP-7 was written in assembly. Early on, programs and tools had to be built under GECOS and transferred to the PDP-7 via paper tape, but once the assembler was complete, Unix became a self-supporting environment in which the next tool could be built on top of the one before it. The file system, process control, shell, and I/O redirection did not emerge all at once from a finished design; they were revised through actual use. Ritchie recalls that because the small team controlled both the I/O system and the shell together, changes that would have been tangled across organizational boundaries in Multics could be tested quickly in Unix.

The PDP-11 approved in 1970 was not purchased with pure operating-system research funds either. What succeeded was a concrete proposal to use the machine, priced at roughly $65,000 and far cheaper than before, for document editing and typesetting. Processing documents for Bell Labs' patent department gave Unix its first institutional customer and justification. While the disk was delayed in arriving, Thompson used the PDP-7's cross-assembler to rewrite Unix for the PDP-11, and that first version was still close to assembly.

Pipes were not part of the original PDP-7 design in finished form either. Doug McIlroy kept pushing the idea of connecting one command's output to the next command's input, and in 1972 pipes and pipelines were added to Unix. Because people working closely together were jointly modifying the operating system, the shell, and small utilities, each layer could be updated together to accommodate the new connection model. The composable approach that became one of Unix's defining strengths was itself a late growth, shaped by actual use and the demands of colleagues.

The PDP-11 pushed B toward C

The high-level language of early Unix was B, which Thompson had derived by trimming down BCPL. The B compilers for the PDP-7 and PDP-11 generated threaded code rather than machine code, and the results were far too slow compared to assembly to serve as a basis for rewriting the kernel and core utilities. The fact that B essentially handled only a single machine word was also a problem on the byte-addressed PDP-11. Types were needed to represent characters, bytes, and the floating-point units that would eventually be attached.15

In 1971, Ritchie added char to B and began building a compiler that generated PDP-11 machine instructions directly. During a transitional stage briefly called NB (new B), int, char, arrays, and pointers were introduced, and by 1972 structures and a more general type system were in place, at which point the name C was adopted. The division of roles, with Thompson creating B and early Unix while Ritchie transformed B into C between 1971 and 1973, is stated explicitly in Ritchie's own HOPL retrospective.15

The compiler could now produce code small and fast enough to compete with assembly, and by the summer of 1973 most of the Unix kernel had been rewritten in C. It would be difficult to say which came first, the language or the operating system. Each time the C compiler matured, more Unix code could be ported over, and problems that surfaced while writing the kernel and utilities fed back into the design of types and libraries. The characteristics of the PDP-11, the limitations of the B compiler, and the demands of real operating system code all interlocked as C and Unix took shape together.

This transition opened a new path: when bringing Unix to a new machine, rather than rewriting the entire operating system in that machine's assembly, one could first prepare a C compiler and then rework only the machine-specific parts, such as device drivers and context switching.15

Loading animated diagram...

Portability did not come for free. The report by Johnson and Ritchie on porting Unix to the Interdata 8/32 shows that a new compiler backend, assembler, loader, debugger, and device drivers all had to be prepared. Even so, roughly 95% of the operating system code outside the device drivers and assembly sections carried over largely intact, and 75–80% of the compiler, assembler, loader, and debugger remained as shared code.15

It was not magic in the sense of "written in C, so it compiles anywhere" (a concept that will be revisited at some length later), but the practical effect was significant: costs that had previously meant a full operating system rewrite were concentrated into a handful of porting layers.

Calling C's portability a single unified property makes it easy to miss what was actually reused and what had to be rebuilt during this process. In practice, what moved was a set of distinct layers.

Layer

Carried over largely as-is

Re-fitted for the new machine

Language and source code

Control structures, functions, structs, and the majority of application code

Integer widths, alignment, endianness, and implementation-defined behavior

Compilation toolchain

Common portions of the front-end and optimizer

Code generator, assembler, and relocation

Unix interface

file descriptor, process, pipe, C library conventions

system call implementation, driver, machine-dependent code

Development environment

shell, make, debugger, text utilities, manual

Booting, device management, packaging and deployment

Layer

Language and source code

Carried over largely as-is

Control structures, functions, structs, and the majority of application code

Re-fitted for the new machine

Integer widths, alignment, endianness, and implementation-defined behavior

Layer

Compilation toolchain

Carried over largely as-is

Common portions of the front-end and optimizer

Re-fitted for the new machine

Code generator, assembler, and relocation

Layer

Unix interface

Carried over largely as-is

file descriptor, process, pipe, C library conventions

Re-fitted for the new machine

system call implementation, driver, machine-dependent code

Layer

Development environment

Carried over largely as-is

shell, make, debugger, text utilities, manual

Re-fitted for the new machine

Booting, device management, packaging and deployment

Here, the C compiler functioned more like a portable joint. It kept all applications from having to deal with a new instruction set directly, and concentrated the machine-dependent code within the narrow boundaries of the compiler backend and the operating system. Code outside those boundaries was not perfectly identical across platforms, but the amount that needed to be rewritten shrank dramatically.

What moved together was more than just source code. The C compiler, shell, linker, debugger, system calls, libraries, make, and text utilities formed a single working environment, and as developers learned C syntax they also absorbed the Unix way of handling files and processes and composing small programs through pipes. C let Unix be reborn on multiple machines, and Unix gave C something tangible: proof that it was not a toy language but one capable of carrying an entire operating system.

1983–1992: Unix compatibility becomes the distribution channel for new systems

The GNU Project was announced in September 1983, with actual development beginning in January 1984. The goal was not to replicate AT&T Unix's source code, but to build an entirely new, free operating system that was compatible with Unix yet could be used, modified, and distributed by anyone. The Unix-family design had already been proven across multiple machines, and the fact that existing users could migrate their knowledge and programs with relative ease was another important reason for choosing Unix compatibility.16

Here, compatibility means more than passively inheriting an aging design. It is also an adoption strategy for bringing existing users' knowledge, shell scripts, source code, and work habits into a new system. GNU produced GCC, GDB, Bash, Binutils, the C library, and numerous utilities in succession; these tools were used on existing Unix systems without waiting for a completed GNU operating system, and in the process each built its own independent user base. By around 1990, the largest missing piece in the GNU system was a kernel that could be used in practice.

Linux was what filled that gap. Released in 1991, Linux started as a Unix-family kernel running on the 386 PC. The Linux 0.01 release notes include instructions for building the kernel with GCC 1.40 and the Bash executable, along with a warning that the kernel alone does not make a usable system.17

From the beginning, Linux was not an isolated kernel project but software built and run on top of the GNU compiler, shell, libraries, and utilities that already existed.

The way this project spread outward is reflected even in the origin of its name. Torvalds had originally intended Freax as the official name, a blend of free, freak, and x, a suffix common in Unix-family names. Linux, a working title combining Linus and Minix, was closer to a temporary label. However, Ari Lemmke at the Helsinki University of Technology, who provided FTP space for distributing the source, created the server directory as pub/OS/Linux rather than Freax. External users downloaded the source from that path, and the name attached to the distribution route eventually solidified as the project's official name. Torvalds himself later recalled that Ari had named the directory Linux the following year and that the name simply stuck.18

What makes this anecdote interesting is not merely that someone arbitrarily changed the project's name, but that the name of the path through which people actually discovered and downloaded the software carried more force than the name the author had intended to give it. We see quite a few cases like this. Ultimately, Linux's identity was not determined solely from within the code; it is more accurate to say that it was also shaped within the distribution network of FTP servers and the internet.

Uploading kernel source to an FTP server alone, however, does not produce a complete operating system. The Linux kernel combined with the compiler, shell, libraries, and utilities that GNU had already accumulated, and various distributions packaged these components into a single system that could be installed and booted. From there, distributions took on responsibility for package management, updates, and hardware configuration, and a Unix-family environment that ordinary users and developers could actually use took shape.

Loading animated diagram...

If you isolate the Linux kernel on its own, more than half of the pathways through which this system spread disappear. What came together in one place was the interfaces that C and Unix had accumulated over decades, the toolchain that GNU had rebuilt as free software, the falling price of 386 PCs, distribution and collaboration over the internet, and the distributions that assembled all of this into a single product.

Riding this convergence, C moved even further away from being the language of any particular company or single operating system. It became the common foundation connecting Unix-family operating systems, GNU tools, network servers, and embedded devices, and compatibility along with the toolchain transformed from a mere technical characteristic into a distribution network that attracted new systems and users.

Looking back at Torvalds's own recollections, when he first developed Linux he was not some grand revolutionary but simply a geek who wanted to run Unix freely on his bedroom 386 PC. The personal project of a young man frustrated by multi-thousand-dollar commercial workstations and a Minix that was functionally limited for educational use met the GNU license, was released as "free" software, and the single biggest change that followed was an explosion in ecosystem scale.

Programmers around the world were now able to build a complete Unix environment on a PC and dig into the code without worrying about expensive licenses or corporate gatekeepers. Yet once this "free Unix" became widely available, a paradoxical situation unfolded. The era of small utilities processing simple text at the terminal gave way to a flood of massive GUI desktops based on the X Window System (KDE and the like), sprawling network daemons, and large-scale databases pouring onto this free operating system.

This explosive uptake pushed the system's complexity past a critical threshold. C was razor-sharp for writing kernels and small Unix tools that "do one thing well," but controlling the state of massive user-space applications ballooning into hundreds of thousands of lines using only global variables and procedural calls was becoming nearly impossible. (Purist C advocates will certainly disagree; they will insist this happened because your C skills were lacking. Your mental health will thank you for graciously accepting that point and moving on.)

What programmers of this new era needed was a higher-level design tool that could encapsulate and compose the structure of bloated software, while leaving intact C's control over memory and full compatibility with the existing Unix ecosystem. The "gigantification of applications" brought on by the newly liberated Unix environment was precisely the most compelling engineering justification for C++ to inherit the lineage of systems programming history.

C++ and Python: Two Ways of Entering the Same Foundation

C++ found its way to writing larger systems within this ecosystem. Stroustrup started from C with Classes in 1979, seeking to bring together the abstraction he had seen in Simula and the system-level access that C provided. The reason the early C++ implementation Cfront emitted C code ties directly to this. By using the C compilers and assemblers/linkers already installed on many machines at the time as a backend, he avoided having to build machine-specific code generators for the new language from scratch. This is the passage where Stroustrup recalled using C as "the most portable assembler."19

As software grew in scale, managing everything with just global state and a list of functions became untenable. When multiple teams needed to maintain the same program over a long period, there was a need to hide internal representations, map domain concepts onto user-defined types, and treat different implementations through a common interface.

As software scaled up, there was a growing need for abstraction that could divide complexity into small units, hide representation from implementation, and enable reuse of components. C++ spread because it addressed that need at the time through object-oriented programming and abstract data types, while never abandoning the existing C ecosystem.

Of course, object-oriented programming was not the only answer; module systems and functional programming addressed the same organizational problems. Where C++ had an advantage in industry was in bringing classes and generic programming inside C's native distribution network. Cfront emitted C code, and GNU's G++, starting in 1987, was distributed across multiple Unix variants alongside GCC, the assembler, the linker, and the debugger.20

Writing a large simulator in Simula, Stroustrup found that classes and class hierarchies let him divide a program into a collection of small, understandable pieces. By contrast, the slow linking and garbage collection of Simula implementations of that era became an increasing burden as scale grew. The early direction of C++ was to carry over that organizing power while retaining, for separate compilation, execution speed, and low-level access, the sensibility of C and BCPL.19

The fact that Cfront emitted C does not end as a mere implementation anecdote: for each new CPU, it was possible to use that machine's C compiler and assembler rather than first completing a dedicated C++ code generator for it.

Constructor calls, virtual function tables, and name mangling were lowered into forms that existing native tools could handle, and the output was the familiar object files and libraries developers already used.

Loading animated diagram...

The connection to C did not stop at the source level. extern "C" and C linkage preserved a narrow boundary for calling operating system APIs and legacy libraries. C++ ABI, on the other hand, had to align class layout, name mangling, exceptions, RTTI, and virtual tables as well, making binary compatibility across compilers and versions a long-standing headache.21

Standardization that began in 1989, the first ISO standard in 1998, and the STL all expanded the common surface across which source and libraries could move. The zero-overhead principle, which held that you pay no cost for features you don't use, was the contract that allowed C users to accept new abstractions.22

The price paid for that was header-based builds, complex object lifetimes, undefined behavior, and template code bloat. Even so, many organizations found it cheaper to layer C++ on top of an existing native toolchain than to migrate wholesale to a new VM or operating system.

Over time, game engines, browsers, databases, and GUI frameworks became Complementary Goods for C++ in turn. C++ initially entered the market by borrowing C's toolchain, but the libraries and engines that accumulated in C++ then pulled subsequent projects inside C++'s orbit. Compatibility was simultaneously a burden that dragged along the past and a distribution network that attracted new users.

But people will say: DOOM was written in C, wasn't it? C can handle seriously complex things just fine!

From Doom to Doom 3

Doom from 1993 looks like a rather inconvenient counterexample for this post. The renderer and game engine were mostly C with some assembly mixed in, and by the standards of the day it was an extraordinarily complex commercial program. If complex software automatically demanded C++, then Doom becomes hard to explain. So before going further, it is worth unpacking what "complex" actually means.

The original Doom's technical difficulty was high: a BSP renderer, fixed-point arithmetic, network play, and multiple platform boundaries all coexisted. Yet the engine's fundamental shape was heavily concentrated in John Carmack's mind and code. Other programmers assisted and designers and artists contributed, but it was nothing like the model where multiple teams independently own separate subsystems and negotiate interfaces over long periods. A tight mental model shared among a small group held the C codebase together.23

Production complexity was separated out sideways rather than piled into a single body of code. id Software worked on NeXTSTEP workstations, and authoring tools such as DoomEd and the BSP node builder were built in Objective-C. The engine and game logic remained in C and were shipped as MS-DOS executables via the Watcom C compiler, while maps, sprites, sounds, and textures were bundled into WAD files. The engine, authoring tools, and content each lived in different languages and file boundaries.24

Loading animated diagram...

NeXTSTEP's Objective-C framework handled rapid construction of editors and internal applications, while C handled a small runtime and direct hardware access. It was an approach that used language and process boundaries to cut complexity apart, before solving every problem inside one massive C++ codebase. This is also why Carmack observed that game development tools can grow larger than the games themselves. Had the tools been forced into DOS C as well, the simplicity visible in the original Doom's C code would have been difficult to preserve.

When the Linux source was released in 1997 and spawned countless ports, much of that success owed to a relatively clean separation between the C core and platform boundaries. Conversely, the DOS version's sound library could not be released due to copyright issues. Even where portable C carried a large share of the code across platforms, external library and device dependencies had to be resolved anew for each port. The long-running joke about Doom running on any conceivable machine came as much from the architecture that let everything around the small C core be swapped out as from the C core itself.24

A decade later, Doom 3 was a different story. Beyond the renderer, sound, physics, animation, networking, and scripting all interlocked with each other, and by Carmack's own account roughly five programmers at one point were each writing major subsystems from scratch. The approach of having one person hold all of the engine's assumptions in their head and fix things on the spot increasingly stopped working. id Tech 4 from that era is said to have moved to C++.23

Carmack already had experience with both C and NeXTSTEP's Objective-C. He recounted entering the language by reading colleagues' C++ code, and traces of the early renderer, originally written in C and then wrapped inside C++ objects, reportedly remained. Even so, he concluded that C++ was well suited to large performance-critical projects involving multiple developers. It was not that classes made the renderer faster; rather, the need had grown to express code ownership, object lifetimes, and contracts between subsystems inside a native codebase.

Loading animated diagram...

When explaining the spread of C++, measuring software scale purely by source line count or algorithmic difficulty invites the Doom counterexample, but the point at which C++ became genuinely necessary was where complexity spread across people and teams, and where different components had to be swapped out independently while the whole system was maintained over a long period. Once the game industry began bundling engine, editor, exporter, asset pipeline, and platform SDK together, C++ became more than the language that produced executables; it became the shared boundary of the production infrastructure. The reason was that it could use C's native distribution network as-is while layering organizational abstractions on top of it.

(I tend to trust what Linus says)

In general, I'd say that anybody who designs his kernel modules for C++ is either

(a) looking for problems

(b) a C++ bigot that can't see what he is writing is really just C anyway

(c) was given an assignment in CS class to do so.

Feel free to make up (d).

So why did the Linux kernel stay with C?

Up to this point I've argued that C++ is better for writing large codebases, but people will point to a counterexample. The Linux kernel is large-scale software maintained by thousands of contributors over many years, yet C remains its core language. If the explanation that projects migrate to C++ as they grow were applied directly, it wouldn't fit. First, we need to distinguish between the Linux kernel and Linux userspace. In userspace, where browsers, desktop applications, databases, and development tools run, C++, Python, Java, Go, and Rust are used freely. The area that remains firmly in C is the kernel itself.

Of course, Linus himself has personal preferences against C++, but the kernel sits below the runtime that ordinary applications take for granted. It provides the memory allocator, threads, interrupts, the scheduler, and device interfaces itself. Linux documentation describes the kernel as a freestanding environment that does not depend on the standard C library, and the actual source uses the GNU C11 dialect and compiler extensions rather than plain ISO C.25

A program that reaches for malloc() or a thread runtime operates under very different assumptions about what it can expect from the language than a program that must build those foundations from scratch.

Writing a kernel in C++, or building kernel modules with a C++ compiler, is not impossible in principle. You simply have to avoid, or provide your own kernel-compatible implementations of, the runtime support needed for the standard library, exception handling, RTTI, dynamic initialization, and the construction and destruction of global objects. Kernels and kernel components written in C++ do exist.

However, C++ constrained in this way diverges considerably from the C++ used in ordinary applications. A project must explicitly define its policies on exceptions, RTTI, dynamic allocation, global constructors, certain standard library features, and the permissible scope of virtual functions and templates, while continuously auditing the code the compiler generates and the resulting ABI. The real question is not whether C++ can be used in a kernel, but who defines the boundaries of the permitted subset and how those boundaries are enforced.

Why does this problem arise? C++ has accommodated very different demands within a single language: low-level memory manipulation, object-oriented programming, generic programming, functional expression, exception handling, and large-scale application development. The result is that features with vastly different costs and execution models coexist within the same syntactic framework. Abstractions that are useful in application code can translate, in kernel context, into hidden memory allocations, exception-handling metadata, static initialization ordering issues, increased code size, or hard-to-predict control flow.

A team can certainly decide that it will use only a subset of C++ features. In a large project, however, documenting the rules is not enough. New contributors and subsystems keep arriving, compilers and language standards evolve, and seemingly simple code may indirectly pull in runtime features that were meant to be off-limits. The team ends up prohibiting, at the project level, options that the language itself offers, and must continuously enforce those prohibitions through static analysis, build configuration, and code review. The complexity of the language is, in effect, transferred into the organizational cost of maintaining control over it.

This is close to the core point Linus Torvalds emphasized in his criticism of C++. His argument was not that building a kernel in C++ is physically impossible, but that if you have to give up many of C++'s features in order to write efficient and portable system code, much of the benefit of choosing C++ disappears in the first place. He also argued that choosing C in itself narrows the available abstractions and execution model, making project discipline a relatively explicit constraint rather than a promise external to the language.26

Some people call this a "straitjacket architecture." (Of course, that person is me.)

Instead of offering too many options and demanding that developers make the right call every time, the approach narrows the available choices upfront so that code can only be written in the way the system does well. A straitjacket reduces expressiveness, but it also reduces the likelihood of poor abstractions, unpredictable execution costs, and lapses in team discipline.

There is also the cost of validating exception unwinding, object initialization, and ABI for each compiler and architecture, effectively layering an additional runtime contract on top of problems Linux has already solved.

Path dependence accumulated since 1991 would compound the problem. Architecture ports, drivers, debuggers, static analysis tools, build systems, and review conventions all grew up around GNU C source, and even as kernel-internal APIs continued to change, the common language among the people handling those changes was C. Even without rewriting tens of millions of lines in C++, the moment both languages coexist, the build matrix grows and the rules reviewers must check multiply, placing a real burden on reviewers. The benefits C++ would provide would need to consistently outweigh these transition costs, and for a long time that calculation simply did not hold for Linux. (Which is precisely what makes the recent introduction of Rust into the Linux kernel so striking.)

Language choice has not frozen entirely. Rust support landed in mainline with Linux 6.1, and the current kernel documentation provides a path for generating C-side bindings and then writing drivers, file systems, and shared abstractions in a no_std environment.25 Rust was brought in not so much for its new syntax as for the prospect of reducing the costs that memory unsafety imposes on driver and concurrency code. The fact that the effort proceeds by testing the boundary where new code enters, rather than ripping out all existing C, reflects exactly that reasoning.

Ultimately, the divergence between C++ and Linux was not decided by scale alone. In game engines, where the runtime and tooling were already in place, the benefits of abstractions that organize multiple subsystems and teams were substantial. The Linux kernel provided that runtime itself and had accumulated the abstractions it needed within long-standing C conventions and GNU extensions. Even with the same level of complexity, what differed was where the costs were concentrated and how much had to be re-validated when the underlying platform changed.

In that context, Python drew on the same ecosystem from a different level. In late 1989, van Rossum began building a scripting language that carried forward the conciseness he had learned from ABC while remaining easy for Unix and C programmers to extend, and he released it to the internet in 1991. The 1995 reference manual describes Python as a rapid-prototyping language that bridges C and the shell and is easy to extend in C.27

Loading animated diagram...

The syntax and execution models of the two languages differ substantially, yet their adoption paths share a common thread: neither demanded that you abandon accumulated C code and operating system APIs to migrate to a new world. C++ chose continuity with native code and existing toolchains, while Python provided a surface for assembling native libraries into larger units. The ability to connect with existing assets lowered the distribution cost of a new language.

Top left: Guro-dong factory district, 1970s

Top right: Guro-dong venture town, 2004

Bottom: 'Two Java [developers], get in...'

1995–2001: Language, Runtime, and Distribution Network Become One Product

Any history of programming languages would be incomplete without Java.

The May 1996 Java white paper presented not just a simple object-oriented syntax but architecture-neutral bytecode, garbage collection, security, dynamic loading, and distributed computing all at once. Java's public release and spread via the web began in earnest in 1995. Even though Oak originally targeted embedded consumer devices, Java at the time of its release positioned network-based code distribution and the JVM as a new software platform.28

C# and .NET bundled a managed execution environment into a single development product during the same period, though they differed from Java in the company behind them, their relationship to the operating system, and their standardization path, so they will be covered in a separate section later.

From this period onward, choosing a language came to mean something closer to choosing a runtime, a standard library, a security model, and packaging and distribution tools all at once. Language syntax was the front face users saw, while the economic switching costs accumulated in the ecosystem behind it.

After 1995: Java Changed Hosts Twice

Java's first business plan targeted set-top boxes and consumer electronics, not data centers. The machines the Sun Green Project faced in 1991 had heterogeneous processors, long product lifespans, and were difficult to patch once deployed in the field. Oak was an attempt to place a small runtime and portable code into that environment. Both the Java Language Specification and the JVM Specification record this consumer-device project as the history that preceded Java.

In The Java Story, Green team members recall the period when they pitched Oak to Time Warner's interactive cable TV venture, failed to win the contract, and faced an uncertain future for the project. The account also mentions that Bill Joy's intervention saved the team from being disbanded. This part is firsthand testimony, not a product specification or court ruling. It is better read as the team's memory of leaving a failed market and searching for the next execution surface than as a precise contractual timeline.29

Loading animated diagram...

Finding a New Purpose on the Web

By the mid-1990s, browsers were already spreading across multiple operating systems. Oak's design—receiving code over the network, verifying it, and delivering the same class file to different machines—turned out to be useful in this context. Sun demonstrated animated content in WebRunner (later HotJava), and Java applet support made its way into Netscape Navigator. The small execution platform originally meant to run inside set-top boxes had effectively been distributed to PCs through the browser.29

The Java developers used a particularly clever approach at this stage: they designed the surface syntax to feel familiar to C and C++ developers. Gosling recalls this in the documentary as "a wolf in sheep's clothing." Behind the curly braces and the class keyword lay garbage collection, bytecode verification, threads, dynamic class loading, and the JVM. Developers started with familiar-looking code, but responsibility for memory and deployment had shifted away from native executables and toward the runtime.

Loading animated diagram...

Write Once, Run Anywhere was Java's marketing slogan, but keeping that promise required real commitments to programmers and real work to uphold them. The class file format, JVM instructions, standard APIs, and compatibility tests all had to be maintained together. If the bytecode stayed the same but the libraries diverged, applications would end up tied to a specific operating system again. Java's portability was not a property that emerged naturally from the syntax; it was the result of platform governance that compelled multiple vendors to honor the same execution contract.

To put it boldly, this had enough reach to effectively make several East Asian countries "Java colonies." As governments, large enterprises, financial institutions, universities, certification markets, and SI firms all began operating with Java as their baseline, an institutional path formed that made choosing any other language increasingly difficult.

The central slogan Write Once, Run Anywhere captured Java's promise to run programs on the JVM rather than binding them directly to a specific operating system and hardware, and that promise was especially attractive to countries that were rapidly pushing forward their digitization efforts.

A fast-growing country's computing environment is rarely unified from the start. The central government runs Unix servers, regional agencies run Windows, banks still have mainframes, and different ministries and companies use databases and middleware from different vendors. In that situation, Java's promise of freedom from operating-system lock-in was not merely a developer convenience; it became a procurement strategy. The J2EE camp of the era actively marketed to enterprise customers the ability to deploy the same application across Windows, Unix, and legacy environments alike.

What a late-developing IT nation needs is not necessarily the most expressive language. More important is a language that can train thousands of developers, allow companies with different ownership to deliver to the same specification, and keep systems maintainable even when the contractor changes. Java was harder to misuse with memory errors than C++, offered a comparatively strong static type system, and hid per-OS build differences behind the JVM. Paired with standard APIs, application servers, database drivers, authentication frameworks, and transaction and messaging technologies, Java grew beyond a single language and closer to an industrial standard for building enterprise information systems.

At this point, Java's advantages cannot be explained by individual developer productivity alone. Clients cared less about "which brilliant developer can build this system" than about "can any vendor build it in roughly the same way." In other words, what mattered was how many ordinary people could be fed into the job market. Universities taught the language industry demanded, training institutions produced Java developers in bulk, and companies hired based on Java experience. Public agencies wrote Java credentials into tender requirements, and SI firms presented their headcount of Java engineers as proof of delivery capability. The moment a technology choice enters the labor market and the procurement system, language adoption stops being pure technical competition and becomes a self-reinforcing cycle. (And as a latecomer, I found myself locked out of that market...)

South Korea offers the clearest illustration of this phenomenon. The eGovFrame (Electronic Government Standard Framework), created by the Korean government, provides a standardized development environment, execution environment, operational tools, and reusable common components for public-sector informatization projects. Rather than letting different ministries each build their own framework, it required systems to conform to a single shared architecture. The World Bank has cited this as an example not of retrofitting connections between individual agencies' systems after the fact, but of first providing a central common architecture and drawing each new system into it.

That choice was rational at the time. It reduced duplicated development, enabled reuse of common functionality, and allowed smaller vendors to participate in public projects by following a defined specification. But when standardization persists long enough, the standard shifts from a recommendation to a de facto entry condition. To gain public-sector project experience you need to know Java; because so many Java developers exist, Java projects keep being commissioned; and because existing systems are written in Java, each successive system is steered toward whatever integrates most easily with Java. What begins as a choice made for compatibility ends, over time, with the ecosystem reproducing the choice on its own.

(A friend of mine who works at Naver, one of Korea's largest tech companies, tells me they use Java there too, and the programmer friends around me all say the same thing. yet I still haven't grown comfortable with Java.)

Compatibility Also Became a Market Boundary

Sensing the threat, Microsoft licensed Java and distributed Visual J++ along with a JVM for Windows. However, by adding Windows-exclusive interfaces and omitting certain standard APIs, Microsoft nudged Java programs toward Windows dependency. The findings of fact in the U.S. antitrust case similarly concluded that this implementation was an attempt to undermine Java's portability and draw developers into a Windows-only environment.30

The separate Java licensing lawsuit filed by Sun was settled in 2001 with Microsoft paying $20 million and phasing out the Microsoft JVM. The $1.95 billion that Microsoft agreed to pay Sun in 2004, however, was not a single damages award for the Java lawsuit; it was a separate comprehensive settlement that bundled the resolution of antitrust and patent disputes, a patent licensing agreement, and technology cooperation arrangements.31

If the JVM provided the same APIs across multiple operating systems, applications could bypass Windows. For Microsoft, this meant the application barrier protecting its operating system would weaken; for Sun, it meant a single independent platform was being fragmented by a Windows-exclusive implementation.

Applets faded; servers remained

Applets served as a showcase that put Java on the map, but they carried persistent problems around installation, security, startup time, and browser integration. The server side was a different story. Organizations could control JVM installation and versioning, and Servlets became a common API bridging HTTP requests and the Java object world. Application servers took on responsibility for transactions, security, connection management, and deployment, and naturally the place where Java made money shifted from the browser to the enterprise server.

The expansion of J2EE quickly created a different kind of cost. Container-centric programming, particularly during the EJB 2.x era, demanded extensive deployment descriptors, remote interfaces, and lifecycle rules. The code accompanying Rod Johnson's 2002 book evolved into the Spring Framework, and Gavin King started Hibernate out of frustration with Entity Beans. Neither project displaced Java EE and claimed empty territory.

The IoC, POJO, and ORM approaches adopted in the field put pressure on existing standards, and Hibernate's design also influenced the later JPA.32

Loading animated diagram...

Some people describe Spring and Hibernate as having "saved" Java, but it is more accurate to say they shifted where developers encountered difficulty within an already large Java market, rather than acting as a rescue team that revived a dying JVM; committee standards and field-driven open source began to revise each other.

A slowdown in releases, then a return to shorter cycles

Calling the entire span from Java 5 in 2004 to Java 8 in 2014 the "lost decade" erases the changes in Java 6 and 7, HotSpot, and the libraries. That said, it is fair to say this was a period when major changes to the language surface were delayed and the intervals between significant releases grew long. Sun's financial difficulties, Oracle's acquisition in 2010, and the slow consensus structure of the JCP all compounded one another.

Java 8 introduced lambda expressions and the Stream API. In 2017 a time-based release cadence was proposed, and starting with JDK 10 a six-month cycle was adopted. Virtual threads, which became a finalized feature in Java 21, are designed to handle blocking I/O concurrency more cheaply without requiring major rewrites of existing thread-per-request code. Some point out that they do not make CPU computation free or speed up every server, but this trajectory is better understood as a response to shifting requirements from the industry itself, involving OpenJDK developers, multiple JVM vendors, and the framework and tooling ecosystem.33

Almost nothing of Java's original plan survived. The set-top box business failed and the applet era ended. Yet the portability, managed memory, dynamic loading, and standard execution contract built for that business outlived it on servers. Each time Java changed hosts, it carried its JVM and libraries further than its original purpose.

After 1995: JavaScript attached behavior to the medium itself

Although it borrowed Java's name, JavaScript was not a smaller Java competing with the JVM. What Netscape wanted was a scripting language that let HTML authors attach behavior directly to page elements and browser objects. Where a Java applet embedded a separately compiled program inside a page, JavaScript manipulated the document's buttons, forms, and window state right there in place. Wirfs-Brock and Eich, summarizing this early history, describe this role as Java's "sidekick scripting language."34

Loading animated diagram...

There was no need to go through the class and applet deployment process just to validate a small input field. You pasted the source into the page and the already-installed browser ran it. That difference in unit of work was significant. No matter how much people complained about JavaScript's syntax, users did not have to install a separate runtime, and because it was convenient, every new web document added another place for JavaScript to run. JavaScript is probably the single most powerful example that reinforces the thesis of this entire post.

Early JavaScript was closer to a language that wired objects inside a host application, much like VBA or HyperTalk, but as the browser became a global deployment surface, its scope expanded from DOM manipulation to networking and complex UIs, and then to Node.js servers and desktop tooling. In other words, as the deployment platform grew, the market grew, and the use cases grew with it. After 2012, TypeScript was able to layer types and language services for large codebases on top of that same execution surface precisely because the JavaScript market had already been laid down beneath it.

2000–2001: C# and .NET, a managed development surface on top of Windows

The initial ECMA standard for C# records that its first broad release was part of the .NET Framework plan in 2000. Around the same time, the C# specification and the CLI specification were being standardized in separate working groups. C# started as the flagship language of .NET, but nothing in the standard required every C# implementation to use Microsoft's CLI.35

Java's portable managed platform put competitive pressure on the Windows-centric development ecosystem as well. Reading C# as nothing more than a response to Java, however, leaves out Microsoft's own internal needs: productivity, type safety, Windows API integration, and a managed execution environment for its server products. Competition was one factor among several, and the reasons for bundling the language and runtime as a single product ran deeper.

Loading animated diagram...

The easier it was for developers to build programs for .NET, the more Complementary Goods accumulated for Windows and Microsoft's server products. Visual Studio, the base class library, ASP.NET, and the deployment tools all lowered adoption costs outside of C# syntax itself. Initially Windows delivered users to C#, and later, as .NET moved toward open source and cross-platform support, the C# ecosystem gained another direction: bringing users back in from outside Windows.

Here too it is hard to separate the market for the language from the market for the Runtime. As more C# code was written, the value of the CLR and .NET APIs rose, and as the .NET library ecosystem grew, the next developer had a reason to choose C#. Just as with Java, the language, the execution contract, and the libraries and tools all became Complementary Goods to one another.

2004–2006: Ruby meets Rails and finally reaches the world, years late

Ruby itself was not born in this period. Matz began the implementation in 1993 and released Ruby 0.95 to a Japanese domestic newsgroup in December 1995. It debuted the same year as Java, but the pace of its global spread was entirely different. Java had Sun, Netscape, and the JVM as its distribution network, while Ruby first had a Japanese-language mailing list and a small community of users.36

This is precisely what illustrates that a language's marketability cannot be explained by the language's own quality alone. No matter how elegantly a programming language is designed, without a path for people to discover it, learn it, and put it to work in real projects, it can remain confined to a local community for a long time. When a language was born, in which linguistic community it first grew, which companies supported its distribution, and what kind of software the market was demanding at the time all directly affect how fast it spreads.

Java spread top-down through enterprises, browsers, and operating systems, whereas Ruby had to depend for a long time on the intrinsic appeal of the language itself. The consistency of its object orientation, blocks and iterators, metaprogramming, and concise syntax were already there, but none of that gave English-speaking developers an urgent reason to learn Ruby. The translation layer between what the language made possible and the concrete outcomes the market demanded was missing.

The turning point was Ruby on Rails, released in 2004. Rails did not merely introduce Ruby syntax; it provided a complete path to building database-backed web applications quickly. Active Record, routing, templates, migrations, and a development server all came inside one framework, and convention over configuration reduced the number of decisions developers had to make. The Rails record from December 2004 shows more than ten thousand downloads in the five months after its initial release.37

Loading animated diagram...

What Rails changed was not Ruby's execution speed but its path to creation. A clear answer to the question "What do you build with this language?" was now attached to it, and a framework that arrived later than the language proceeded to remake the language's market.

The timing was right as well. The web was no longer an information system that only large enterprises could build; it was becoming a space where small teams could create a service in a short time and test it against the market. What mattered to them was not a language's theoretical completeness or peak execution performance, but how quickly they could turn an idea into a data model and a screen. Rails was able to use Ruby's flexible object model and metaprogramming to hide repetitive configuration and glue code inside the framework. It was the moment when the characteristics Ruby had possessed all along met the concrete market of the web startup.

2006–2015: Rust attempts to draw a safe boundary between C and C++

Rust started around 2006 as a personal project by Graydon Hoare, went through Mozilla's sponsorship and research on the Servo browser engine, and released version 1.0 in May 2015. The 1.0 announcement led with a combination of low-level control, memory safety, and an execution model without a Garbage Collector. It aimed to be a systems language capable of replacing C libraries while not asking developers to abandon the C ABI or native toolchain. The cost of safety was shifted toward ownership, borrow checking, and compile time.38

Early Rust suffered from frequent language changes that made long-lived programs hard to maintain. The 1.0 release therefore mattered more for its stability promise than for any feature list. With Cargo, crates.io, and a six-week release train all settling into place at once, the language moved from shipping only a compiler to delivering a full development environment bundled with package management and documentation.

Rust did not first carve out a separate operating-system market; rather, it shaped a new kind of demand within existing platform markets, filling in the limitations of established infrastructure. After proving implementation techniques in Firefox and Servo, it moved into the system boundaries previously held by C and C++. The subsequent process by which Android, Windows, and Linux began accepting Rust for new components and drivers is revisited in the memory-safety section later.

2007–2012: Go attempts to reduce the overhead of large-scale server development

Go's design began at Google in 2007, was released as open source in November 2009, and reached Go 1 in March 2012. Rob Pike's retrospective describes the problems of that era in fairly concrete terms. Thousands of engineers were working with tens of millions of lines of C++, Java, and Python code, and even with a build cluster, compilation took minutes or hours. Multicore CPUs and network services were multiplying, yet the approaches to managing code and dependencies could not keep up with that scale.39

What Go sought to reduce was not just execution time. It bundled a fast compiler, explicit package dependencies, a single formatter in gofmt, documentation tooling, and a test command directly into the language distribution. Goroutines and channels expressed concurrency for network services, while garbage collection shifted part of resource management in server code to the runtime. Rather than accumulating sophisticated type features, the language opted to get an entire team using the same tools and conventions.

Loading animated diagram...

Once Go 1's compatibility promise was in place, external organizations could commit long-lived codebases to it. Cloud-native tools such as Docker and Kubernetes were then written in Go, creating a new distribution channel, and large service companies like ByteDance of TikTok added their own internal RPC and network frameworks to the Go ecosystem. The language that had been built to solve Google's problems migrated to become the shared language of cloud infrastructure tooling.39

The 2010s: riding existing platforms rather than building new ones

During the 2010s, a number of languages emerged that changed the authoring experience while preserving already-widespread runtimes and libraries, rather than establishing a new runtime from scratch. This approach was far cheaper when it came to bringing existing users along.

Standing up a new platform requires far more than a compiler. You need a runtime, a standard library, a debugger, a package repository, an IDE and build system, an FFI for calling native libraries, a distribution format, and a security update policy. In practice, once industry standards already exist, replacing all of that is nearly impossible, not only because existing programmers have jobs tied to the current infrastructure, but because no one can reliably estimate how much it would cost to rip out every layer. Beyond that, simply creating a language is only the beginning: documentation, education, a hiring market, and a compatibility promise that will not break for years must all be sustained in parallel. The real cost continues for as long as it takes for people to trust the language enough to commit their programs to it.

For that reason, recent languages often ride on top of an existing platform rather than trying to displace it. Even if a new language offers a better type system or concurrency model, it relies underneath on already-proven runtimes, OS APIs, browsers, or mobile SDKs. Being able to call existing code and packages directly is what lets an organization try switching one module at a time. A language that demands a full migration takes on platform-building costs before the first program even runs.

TypeScript is a good illustration of this. Released in October 2012 and reaching 1.0 in 2014, it did not move JavaScript code to a different runtime; instead, it layered static types and language services onto the development phase and then emitted standard JavaScript. Because it used browsers, Node.js, and npm packages as-is, adopters had no need to swap out their execution platform. The declaration files of DefinitelyTyped became a community mechanism for overlaying type information on existing JavaScript libraries without discarding them.40

Kotlin took a similar path. The project JetBrains started in 2010 announced its 1.0 release in 2016, putting JVM bytecode and Java interoperability front and center. Existing Java codebases did not need to be migrated all at once, and IntelliJ's refactoring tools and build tooling carried over. When Google made Android development Kotlin-first in 2019, Android Studio, Jetpack, and the educational ecosystem became a distribution channel that prioritized Kotlin.41

Swift was introduced by Apple in 2014 and landed inside Xcode and the Apple SDK. Rather than eliminating the Objective-C runtime and frameworks at a stroke, it interoperated with them while offering new syntax, type safety, and a revised memory model. Just as TypeScript built on JavaScript and Kotlin built on Java and the JVM, Swift became the next language in an already-installed Apple development path.42

The three languages differ in design and purpose, yet they share a common approach to reducing adoption costs: developers did not have to abandon existing platform APIs, libraries, or user markets in order to adopt the new language. Each made the choice to assimilate into a successful platform. The language competition of the 2010s was less often a race between new runtimes on empty ground and more often a contest over who could better improve the authoring tools of an already-successful platform.

Go and Rust, too, do not stray far from this economic logic, though to varying degrees. Go bundled its own compiler, runtime, and standard tooling, but its deployment surface was existing operating systems and cloud infrastructure. Rust built Cargo and its own ecosystem while still entering existing system software through LLVM, native linkers, and the C ABI. This is not to say that recent languages build nothing new, but the general orientation is toward minimizing the number of layers that must be created from scratch and reusing execution surfaces that have already been laid down at great expense.

Embedding Memory Safety into Existing Platforms

The earlier Rust timeline briefly traced the adoption path for bringing memory safety into existing systems. Here we go a little deeper into what that path looked like in policy documents and in the actual transitions underway in Windows, Android, and Linux. Rust is a superstar watched by C++ documentary makers, countless language developers, and luminaries such as Anders Hejlsberg, so the subject is well worth the closer look.

Language choice has long been treated as a matter of technical taste or productivity within development organizations. As memory safety issues led to recurring vulnerabilities in operating systems, browsers, and telecommunications equipment, and as IT became increasingly intertwined with real-world industries, the security exposure of critical information grew into a serious concern.

Why the White House Report Named a Language

The White House Office of the National Cyber Director (ONCD) published a technical report in 2024 titled Back to the Building Blocks. The report directly names languages such as C and C++ for lacking memory-safe properties despite their widespread use in critical systems, and recommends treating memory-safe languages as an early architectural decision in new products. It also cites industry data showing that up to 70% of CVE-assigned vulnerabilities were memory safety issues.43

Summarizing this document as "the White House banned C and C++" is inaccurate, though not wildly off the mark. The report separately addresses domains such as space and embedded systems where toolchain constraints, deterministic execution timing, and hardware access leave little room for alternatives, and it does not call for rewriting legacy code all at once. It proposes a mixed strategy: start new code in a safe language, and for existing systems migrate high-risk functions and libraries first. A 2023 joint document from CISA, the NSA, the FBI, and several other national agencies likewise recommends that manufacturers produce and publish a memory-safety roadmap for each of their products.44

Here, language becomes a unit of procurement and supply-chain policy rather than merely a tool for individual programmers. If repeatedly hunting for the same class of defects through static analysis and code review is expensive, the calculation is straightforward: change the default to a language that makes those defects difficult to express in the first place. Modern C++ can address these problems, and careful enough developers can prevent such security bugs, but as software grows more complex it becomes impossible to rely on human vigilance alone, making this the obvious industrial choice.

How misguided is it to argue that every problem can be solved by using a chainsaw safely without a safety guard? Acknowledging that humans cannot always operate safely and fitting a safety guard is simply the right industrial judgment.

Rust: Moving the Safety Boundary Without a Garbage Collector

Rust 1.0 shipped in 2015. Through ownership and borrow checking, it verifies much of the lifetime and aliasing relationship at compile time, preserving the memory control that systems programming demands without a traditional Garbage Collector.38 This combination is precisely why Rust is so often invoked in domains such as operating systems that require predictable performance and direct hardware access.

That said, Rust code is not automatically safe across the board. unsafe blocks, FFI through the C ABI, poorly designed safe wrappers, logic errors, and supply-chain vulnerabilities all remain. Microsoft's early adoption notes also recorded that C/C++ boundaries enter through unsafe, so they must be confined to small wrappers and reviewed separately. Problems with legacy build systems not aligning with Cargo, and complex C++ abstractions leaking across FFI boundaries, were real issues as well.45

Rust resolves many security issues, but human reasoning still has to be part of programming. What it does is mark and narrow the scope of dangerous operations that require review, while handing the compiler responsibility for repetitive checks in the remaining code. The share of responsibility that can be shifted from humans to the compiler has grown.

Recurring Adoption Patterns in the Timeline

The same language can move across multiple categories as its era and host change. Java created a new runtime as its market, yet it also rode deployment surfaces called the browser and the server. Rust builds its own package ecosystem while exploiting the C ABI and the component boundaries of existing operating systems.

Adoption Patterns

Costs Targeted for Reduction

Representative Cases

Newly Introduced Costs

Making expensive machines accessible to more people

Machine code authoring and programming time

Fortran and the IBM 704

Compiler quality, portability across vendors

Platform and language carry each other forward

Operating system porting and application portability

C and Unix, GNU/Linux

Backends, drivers, implementation-defined behavior

Raising the ceiling of an existing platform

The cost of building a new Runtime and distribution network from scratch

C++, Python

ABI, FFI, backward compatibility

Turn a new runtime into an application marketplace

The cost of having application code handle OS and CPU differences directly

JVM, .NET

Runtime installation, and control over standard API compatibility

Attach behavior to already-distributed media

User installation and deployment overhead

JavaScript and the browser

Host API dependency, and control by media platform operators

The framework prescribes what the output will look like

Assembly and configuration, and variation in team-level conventions

Ruby on Rails

Dependency on the framework's lifespan and conventions

The compiler prevents recurring defects

Code review and security incident remediation costs

Rust's memory safety

Learning curve, compile time, FFI and unsafe boundaries

Reduces development overhead through standardized tooling

Organizational friction from build, format, test, and dependency management

Go

The cost of the language pre-closing certain choices

Changes the authoring experience while preserving the existing execution surface

Full migration and ecosystem rebuilding

TypeScript, Kotlin, Swift

Inherits the constraints of the underlying platform along with it

Adoption Patterns

Making expensive machines accessible to more people

Costs Targeted for Reduction

Machine code authoring and programming time

Representative Cases

Fortran and the IBM 704

Newly Introduced Costs

Compiler quality, portability across vendors

Adoption Patterns

Platform and language carry each other forward

Costs Targeted for Reduction

Operating system porting and application portability

Representative Cases

C and Unix, GNU/Linux

Newly Introduced Costs

Backends, drivers, implementation-defined behavior

Adoption Patterns

Raising the ceiling of an existing platform

Costs Targeted for Reduction

The cost of building a new Runtime and distribution network from scratch

Representative Cases

C++, Python

Newly Introduced Costs

ABI, FFI, backward compatibility

Adoption Patterns

Turn a new runtime into an application marketplace

Costs Targeted for Reduction

The cost of having application code handle OS and CPU differences directly

Representative Cases

JVM, .NET

Newly Introduced Costs

Runtime installation, and control over standard API compatibility

Adoption Patterns

Attach behavior to already-distributed media

Costs Targeted for Reduction

User installation and deployment overhead

Representative Cases

JavaScript and the browser

Newly Introduced Costs

Host API dependency, and control by media platform operators

Adoption Patterns

The framework prescribes what the output will look like

Costs Targeted for Reduction

Assembly and configuration, and variation in team-level conventions

Representative Cases

Ruby on Rails

Newly Introduced Costs

Dependency on the framework's lifespan and conventions

Adoption Patterns

The compiler prevents recurring defects

Costs Targeted for Reduction

Code review and security incident remediation costs

Representative Cases

Rust's memory safety

Newly Introduced Costs

Learning curve, compile time, FFI and unsafe boundaries

Adoption Patterns

Reduces development overhead through standardized tooling

Costs Targeted for Reduction

Organizational friction from build, format, test, and dependency management

Representative Cases

Go

Newly Introduced Costs

The cost of the language pre-closing certain choices

Adoption Patterns

Changes the authoring experience while preserving the existing execution surface

Costs Targeted for Reduction

Full migration and ecosystem rebuilding

Representative Cases

TypeScript, Kotlin, Swift

Newly Introduced Costs

Inherits the constraints of the underlying platform along with it

It is worth noting that a single language does not always fall into just one category.

This is simply a rough distinction the author made for ease of understanding, not a rigorous definition.

C++ borrowed the existing C toolchain at the outset, but over time game engines, databases, and GUI frameworks drew in new users and built an independent ecosystem. It began as a way to raise the ceiling of an existing platform, and eventually C++ itself became the platform that subsequent languages and tools had to align with.

Java did not stay in one category either. Oak's consumer-electronics runtime, browser applets, and enterprise servers were distinct markets, yet the JVM held them together by maintaining the class file and library contract in the middle, allowing the application market to persist even as it shifted hosts.

Rust's compiler enforces part of memory safety, but actual adoption has followed the boundaries of C/C++ systems. The process of entering through components, drivers, and libraries rather than rewriting all of Windows at once is a clear example. Both patterns appear together here: reducing the cost of security incidents and riding on top of an existing platform.

Go packaged the compiler, runtime, formatter, and test and documentation tools as a single bundle. Rather than distributing a new operating system, it used existing OSes and the cloud as its deployment surface, and Docker, Kubernetes, and middleware from large service companies in turn expanded Go's distribution network.

Type alone cannot fully determine the origin of a language; research languages sometimes take theorem provers, AI labs, or academic communities as their host rather than commercial platforms. Politics, licensing, standards committees, educational systems, and chance failures account for the rest.

Why I call them "Authoring Tools"

In this table, "medium" refers not to media in the general sense but to the surface on which a program runs and meets its users.

Execution Medium

Representative Authoring Tools

What Gets Produced

IBM Mainframe

Fortran compiler

Scientific computing programs

Unix

C compiler, shell, toolchain

Operating systems and system utilities

JVM

Java compiler and API

Portable applications

.NET

C#, CLR, SDK

Managed desktop and server applications

Browser

HTML, CSS, JavaScript

Web documents and web applications

Apple OS

Swift, Xcode, SDK

App Store applications

Execution Medium

IBM Mainframe

Representative Authoring Tools

Fortran compiler

What Gets Produced

Scientific computing programs

Execution Medium

Unix

Representative Authoring Tools

C compiler, shell, toolchain

What Gets Produced

Operating systems and system utilities

Execution Medium

JVM

Representative Authoring Tools

Java compiler and API

What Gets Produced

Portable applications

Execution Medium

.NET

Representative Authoring Tools

C#, CLR, SDK

What Gets Produced

Managed desktop and server applications

Execution Medium

Browser

Representative Authoring Tools

HTML, CSS, JavaScript

What Gets Produced

Web documents and web applications

Execution Medium

Apple OS

Representative Authoring Tools

Swift, Xcode, SDK

What Gets Produced

App Store applications

A language is a bundle of trade-offs, and it comes packaged with infrastructure. APIs, debuggers, package formats, documentation, and distribution channels all need to be present before anything can be produced consistently. In that sense, a language is both an expressive system and the centerpiece of an authoring tool bundle.

(Jyri, the mascot of my language that I hope to finish someday)

Conclusion: A language cannot build a world on its own

Reading the history of programming languages purely as a progression of syntax and paradigms leaves too many important episodes unexplained. Why did more sophisticated languages disappear while heavily criticized ones survived for decades? Why did Java and Ruby, released in roughly the same era, spread at such different speeds, and why did Ruby only become a global language after meeting Rails? Why did C spread alongside Unix, and how did JavaScript, despite countless criticisms of its design flaws, become the sole native language of the web?

The answer does not lie within the language alone.

In each era, a language served as a mechanism for shifting the most expensive costs elsewhere. Fortran transferred the cost of writing machine code to the compiler; C concentrated the cost of rewriting operating systems for each machine into the compiler backend and a thin portability layer. Java delegated the differences between operating systems and processors to the JVM, while Python shifted the cost of assembling native libraries to a higher level of abstraction. Rust aims to move the burden of repeated memory safety checks from programmer vigilance to the compiler.

No cost has ever truly disappeared; it has only moved. Garbage collection reduces the burden of freeing memory, but introduces a runtime and pauses. A virtual machine hides deployment differences, but requires managing runtime compatibility. A strong type system catches errors before execution, but demands more explicit annotations and longer compile times. A framework reduces configuration, but binds you to that framework's conventions and lifecycle.

Language design, therefore, is not an exercise in expanding freedom without limit. It is the work of deciding which freedoms to leave with the programmer, which choices to delegate to the compiler and runtime, and what to make inexpressible altogether. Every language is a constrained space carved from the possibilities the machine permits. A language's worldview is ultimately the designer's answer to the question of where a programmer's limited cognitive resources should be spent.

Yet technically sound cost allocation alone does not guarantee success. A language needs a host to carry it. C had Unix; Java had the JVM and enterprise information systems; JavaScript had the browser. C# had Windows and Visual Studio, Swift had Apple's devices and the App Store, and Kotlin had the JVM and Android. Ruby only found a clear market in web applications after it met Rails.

A person learning a language is not just choosing a syntax. They are also choosing the operating system, runtime, libraries, frameworks, IDE, package repository, and job market that the language connects to. The same is true for organizations. Adopting a language is not a matter of picking source code; it is a decision about how to train and hire people going forward, which vendors to trust for maintenance, and how to integrate with existing assets.

At this stage, compatibility becomes not merely a technical virtue but a market necessity. A language that can draw on existing code, knowledge, tools, and people has an advantage over a new language even if it is less elegant. Conversely, a language that requires discarding every existing ecosystem and building a new runtime, libraries, debugger, and deployment pipeline from scratch starts out carrying enormous debt before it can deliver its first useful program.

When reading the history of programming languages, then, three questions must be kept carefully distinct throughout.

First, what problem was the language created to solve?

Second, why did developers and organizations actually adopt it?

Third, what kept the language dominant even after the original problem had disappeared?

The answers to these three questions are usually different. Java started on set-top boxes but built its empire on enterprise servers; JavaScript began as a small browser scripting tool but became the execution surface for web applications. C was a language for writing Unix, yet as Unix and the GNU toolchain spread, it became the common foundation for countless systems. A language's original purpose brings it into existence, but the hosts and Complementary Goods that follow determine its fate.

In the end, a successful programming language is not the most perfect one. It is a language that solves a sufficiently useful problem, connects to the existing world, offers a short path to real results, and earns the trust that it will not break for a long time. Syntactic elegance matters, but it cannot create a market on its own.

A language is a tool for writing the world, but no language can fill that world by itself. The compiler and runtime, frameworks and libraries, the operating system and hardware, education and hiring, corporations and governments, and sometimes a chance failure all build that world together.

So if I ever finish building a new language someday, I must think about more than the syntax. I will need to design, together: what problems the language solves better than others, which existing ecosystems it can build on, what a user can create on the very first day, and why the next person should bother learning it too.

Building a language is not the act of inventing a new syntax.

It is the act of creating, simultaneously, a world that people can enter and live in, and the path that leads them there.

And yet, people always seek something new. And into that newness they project the struggles they have lived through.

Every language bears, to some degree, the scars of its creator. Someone who could no longer endure repeated memory corruption builds safe ownership; someone exhausted by endless configuration creates convention; someone fed up with towering class hierarchies separates data and function once more. What was freedom for one person becomes a danger to be eliminated for another.

Reading the grammar of a new language is therefore much like reading a detective novel in search of what frustrated its designer. More than what it lets you write concisely, what it forbids you from writing, what the compiler checks on your behalf, and which error messages received special care, all reveal what that person was fighting against in the world they came from.

But wounds alone cannot found a city. A perfect refuge for one person may be an unbearably narrow room for another, and the wall erected to eliminate one problem may also block the road into an entirely new market. For a language to become a world, it must move beyond its designer's pain and meet the work of others.

Someday I want to finish a language of my own. It will almost certainly carry the frustrations and desires I accumulated while working with C++, C#, Python, and TypeScript: what I wanted to prove, what I wanted to erase, what responsibilities I hoped to free programmers from, all of it will be inscribed in the grammar and the compiler.

But if that language never leaves my own world, it will amount to nothing more than an elaborate autobiography.

And somewhere out there, someone else will be straining just as hard to show their own world to others.

A programmer is the sole legislator of their own world, but also a glory-hungry, lonely autocrat who strains desperately to show that world to someone else.

Because a law that governs no people holds no power at all.

Footnotes

  1. U.S. Department of Justice trial record stating that IBM's OS/2, despite being technically functional, failed to attract OEMs and users due to a lack of applications https://www.justice.gov/atr/us-v-microsoft-proposed-findings-fact
  2. Joel Spolsky. Strategy Letter V. Explains the strategy of lowering the price of Complementary Goods to increase demand for the primary product.
  3. Alan J. Perlis. Special Feature: Epigrams on Programming. ACM SIGPLAN Notices 17(9), 1982, pp. 7-13. The statement "A language that doesn't affect the way you think about programming is not worth knowing" is epigram number 19. Full text available here.
  4. Python Software Foundation. Extending Python with C or C++. Describes the structure by which C extension modules implement new built-in object types and expose C libraries and system calls to Python. This interface is specific to CPython and is not directly compatible with other Python implementations.
  5. John W. Backus et al. The FORTRAN Automatic Coding System. Proceedings of the Western Joint Computer Conference, 1957, pp. 188-198. A contemporaneous report presenting the Fortran language and Compiler for the IBM 704.
  6. CODASYL. COBOL: Initial Specifications for a Common Business Oriented Language, 1960. The original COBOL specification, authored by a joint committee of government and industry representatives.
  7. John McCarthy. Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I. Communications of the ACM 3(4), 1960, pp. 184-195. DOI: 10.1145/367177.367199.
  8. J. W. Backus et al., edited by Peter Naur. Report on the Algorithmic Language ALGOL 60. Communications of the ACM 3(5), 1960, pp. 299-314; Revised Report, 1963.
  9. John G. Kemeny, Thomas E. Kurtz. BASIC: A Manual for BASIC, the Elementary Algebraic Language Designed for Use with the Dartmouth Time Sharing System. Dartmouth College Computation Center, 1964.
  10. Computer History Museum. The First Reference to Simula in Writing Is Made, first written mention in 1962; Ole-Johan Dahl, Kristen Nygaard. SIMULA: An ALGOL-Based Simulation Language. Communications of the ACM 9(9), 1966, pp. 671-678. This paper introduces Simula I and should be read separately from the completed Simula 67. For the connection between the two languages and how object-oriented concepts were consolidated in Simula 67, Ole-Johan Dahl's retrospective The Birth of Object Orientation: The Simula Languages, 2001, was also consulted.
  11. Alan C. Kay. The Early History of Smalltalk. ACM SIGPLAN Notices 28(3), HOPL-II, 1993, pp. 69-95. A retrospective paper in which the designer later documents the development work of the 1970s.
  12. Robin Milner. A Theory of Type Polymorphism in Programming. Journal of Computer and System Sciences 17(3), 1978, pp. 348-375; Michael Gordon, Robin Milner, Christopher Wadsworth. Edinburgh LCF, 1979.
  13. Alain Colmerauer, Philippe Roussel. The Birth of Prolog. ACM SIGPLAN Notices 28(3), HOPL-II, 1993, pp. 37-52. A retrospective documenting how Prolog emerged from a natural language processing project in 1971-1972.
  14. Dennis M. Ritchie. The Evolution of the Unix Time-sharing System. Presented in 1979 and later reprinted in AT&T Bell Laboratories Technical Journal 63(6), 1984. Documents the Multics withdrawal, the early PDP-7 system, the PDP-11 acquisition justified by document processing needs, and the evolution of file, process, shell, and I/O design; Dennis M. Ritchie. The Space Travel game. A separate retrospective that straightforwardly explains how the game led to the discovery of the PDP-7 and the early Unix work.
  15. Dennis M. Ritchie. The Development of the C Language. HOPL II, 1993. Explains how B's threaded code and the PDP-11's byte addressing necessitated types and a native-code compiler, the rewriting of the Unix kernel in C in 1973, and subsequent portability work; S. C. Johnson, D. M. Ritchie. Portability of C Programs and the UNIX System. Documents the code kept common and the machine-dependent portions written anew during the Interdata 8/32 porting effort.
  16. Free Software Foundation. Overview of the GNU System; Richard Stallman. The GNU Project. Consulted for the announcement and development timeline of GNU, the rationale for choosing Unix compatibility, and the process by which user-space tools such as GCC, Bash, and the C library were built first.
  17. Linus Torvalds. Linux 0.01 Release Notes, 1991. Contains the build environment for the early 386 kernel, its dependencies on GCC and Bash, and the note that the kernel alone does not constitute a usable system.
  18. Linus Torvalds. "How to pronounce 'Linux'?", comp.os.linux, 1992-04-23. Torvalds recalls that he had originally intended the name Freax, but Ari Lemmke created the directory as pub/OS/Linux on the FUNET FTP server, and that name stuck.
  19. Bjarne Stroustrup. A History of C++: 1979-1991. ACM SIGPLAN Notices 28(3), HOPL-II, 1993, pp. 271-297. Covers the design constraints from C with Classes through C++ up to just before standardization.
  20. GNU Project. GCC Releases. A record of early GNU C++ Compiler releases, including the December 1987 release of g++ 1.15.3; GCC. G++ and GCC.
  21. Bjarne Stroustrup. Evolving a language in and for the real world: C++ 1991-2006. HOPL III, 2007. Covers the decision to retain the existing linker and object-file model, ABI issues, and changes following the STL and standardization.
  22. ISO/IEC JTC 1/SC 22/WG 21. Current Status and Published C++ Standards; Bjarne Stroustrup. An Interview with Bjarne Stroustrup. Consulted for the background on the 1989 U.S. committee, the 1991 ISO joint effort, the first standard in 1998, and the adoption of the STL.
  23. Fabien Sanglard. Doom 3 Source Code Review: Interviews, 2012. John Carmack recalls his path from C and Objective-C experience into C++, the id Tech 4 transition to C++, and the organizational changes that arose as multiple developers took ownership of separate subsystems built on a C-originated renderer.
  24. id Software. DOOM Open Source Release, John Carmack's release statement dated December 23, 1997. Explains why only the Linux source was released, the external sound library issue with the DOS version, and the portability of the code; John Carmack. Id's John Carmack on: NEXTSTEP, 1994. A first-hand account of the reasons for choosing NeXTSTEP as the authoring environment and the scale of the game tools; Apple. What Is Cocoa? - A Bit of History. Describes the scope of NeXTSTEP as an operating environment and the Objective-C class library lineage of the Application Kit, Sound Kit, and Music Kit; Fabien Sanglard. Doom Engine Source Code Review, 2010. A source-level walkthrough of DoomEd and the BSP builder's Objective-C implementation, production on NeXTSTEP with DOS builds via Watcom C, and the connection between the WAD format and the renderer.
  25. Linux Kernel Documentation. Programming Language and HOWTO do Linux kernel development. Explains that the kernel uses the GNU C11 dialect with compiler extensions and operates as a freestanding environment without depending on the standard C library; Linked Lists in Linux and Device Driver Design Patterns. Show the practical form of intrusive lists, container_of, and C struct-based driver conventions; Rust in Linux 6.9, Rust, and General Information. Explain the Rust support introduced in Linux 6.1, the no_std constraint, and how abstractions for drivers and file systems are built on top of C bindings.
  26. See: Linus Torvalds, "Re: Compiling C++ kernel module + Makefile," Linux Kernel Mailing List, 2004-01-20.
  27. Guido van Rossum, Jelke de Boer. Interactively Testing Remote Servers Using the Python Programming Language. CWI Quarterly 4(4), 1991, pp. 283-303. The first paper to cover Python; Guido van Rossum. Python Reference Manual, CWI Report CS-R9525, 1995; Foreword on the origins of Python, 1996.
  28. James Gosling, Henry McGilton. The Java Language Environment. The Oracle-preserved version of the Java white paper Sun Microsystems published in May 1996; Oracle. Java Language Specification Preface; JVM Specification Preface. Consulted to confirm Oak's origins in embedded consumer electronics and its lineage leading to the JVM.
  29. CultRepo. The Java Story | The Official Documentary. An interview documentary covering Oak and the set-top box (02:04), the web and Netscape (06:05), Gosling's "wolf in sheep's clothing" recollection (12:27), servlets (17:46), and J2EE and open-source frameworks (31:53). Although the video title says Official Documentary, it was not treated in this post as a sole official Oracle record; Oracle documents and court and project records were cross-referenced alongside it.
  30. U.S. Department of Justice. U.S. v. Microsoft: Court's Findings of Fact, 1999. Covers how Microsoft's Java implementation and development tools for Windows undermined Sun's cross-platform API and steered developers toward Windows-only interfaces; James Gosling. Statement in U.S. v. Microsoft, 1998.
  31. Microsoft. Microsoft Reaches Agreement to Settle Contract Dispute With Sun Microsystems, 2001. Documents the $20 million settlement of the Java technology licensing dispute; Microsoft. Microsoft and Sun Microsystems Enter Broad Cooperation Agreement, 2004; Microsoft 2004 Annual Report. Financial Review, Note 17. The $1.95 billion payment in 2004 was not damages solely for the Java contract case; it was a bundled figure covering multiple disputes along with patent and cooperation agreements.
  32. Rod Johnson. Spring Framework: The Origins of a Project and a Name, 2006; Spring Framework. Framework Overview: History; Hibernate ORM. A Short Guide to Hibernate 7: The early history of Hibernate and JPA. Consulted to trace how Spring developed as a response to the complexity of the early J2EE specifications and how Hibernate grew from dissatisfaction with Entity Beans to influence the JPA standard.
  33. Oracle. What's New in JDK 8; Mark Reinhold. Accelerating the JDK release cadence, 2017; OpenJDK. JDK 10 General Availability, 2018; Ron Pressler, Alan Bateman. JEP 444: Virtual Threads, delivered in JDK 21. Consulted for the goals and scope of lambdas, the six-month release cadence, and virtual threads.
  34. Allen Wirfs-Brock, Brendan Eich. JavaScript: The First 20 Years. Proceedings of the ACM on Programming Languages, 4(HOPL), 2020.
  35. Ecma International. ECMA-334, C# Language Specification, 1st edition, 2001; Microsoft. C# language specification: Introduction.
  36. Ruby. Official Ruby FAQ: What is the history of Ruby?; About Ruby. Consulted for the language's start in 1993, the public release of Ruby 0.95 on a Japanese domestic newsgroup in 1995, the mixed lineage of the language design, and the role Rails played in its international spread.
  37. Ruby on Rails. Rails celebrates more than 10,000 downloads, December 29, 2004. A cumulative figure approximately five months after the first release; Rails 0.9: Fast development, breakpoints, validations, December 16, 2004. Documents how the early framework bundled a development server, live reload, validations, and a cohesive web application workflow.
  38. The Rust Core Team. Announcing Rust 1.0, 2015; Five Years of Rust, 2020; Rust Standard Library. `unsafe` keyword; Microsoft Security Response Center. Why Rust for Safe Systems Programming, 2019.
  39. Rob Pike. Go at Google: Language Design in the Service of Software Engineering, SPLASH 2012; Rob Pike, Andrew Gerrand. The Path to Go 1, 2012; The Go Project. Go FAQ: Origins; Go for Cloud & Network Services. Consulted for the design start in 2007, the public release in 2009 and Go 1 in 2012, Google's large codebase, build time, multicore, and network service problems, and the subsequent distribution channels through Docker and Kubernetes.
  40. Microsoft TypeScript Team. Ten Years of TypeScript, 2022; Jonathan Turner. Announcing TypeScript 1.0, 2014. Consulted for the initial public release in 2012, the JavaScript compatibility principle, types and language services for large-scale applications, and distribution via Visual Studio, npm, and source.
  41. Kotlin. FAQ. Describes the project's start in 2010, version 1.0 in 2016, and JVM and Java interoperability; Android Developers. Android's Kotlin-first approach. Describes the policy, adopted from 2019, of providing Android tools, documentation, and Jetpack APIs with Kotlin as the primary language.
  42. Joe Groff. Evolving Swift On Apple Platforms After ABI Stability. Swift.org, 2019; Swift.org. Platform Support; Create Embedded Software with Swift; Swift official website.
  43. White House Office of the National Cyber Director. Back to the Building Blocks: A Path Toward Secure and Measurable Software, 2024. A policy technical report covering memory-safe languages, hardware protections, and formal methods together.
  44. CISA, NSA, FBI et al. The Case for Memory Safe Roadmaps, 2023. Sets out the reasons and process by which software manufacturers should develop and publish memory safety transition plans.
  45. Microsoft Security Response Center. Using Rust in Windows, 2019. Documents FFI, unsafe, build system, and organizational review challenges encountered during experimental migration of low-level Windows components.