Whatever happened to C?
A few years ago, anyone who wanted to become a programmer kept hearing the same advice: learn C. It was the rite of passage. Pointers, malloc(), segmentation faults at two in the morning. If you survived C, you could survive anything.
Today a kid who wants to get started opens an editor, types "build me an app that tracks my expenses", and thirty seconds later has something that works. They have never seen a pointer. They may not even know which language the code in front of them is written in.
So the question is a fair one: whatever happened to C? And more generally: does it still make sense to study a programming language, now that computers have finally learned to understand ours?
This article is for anyone thinking about getting started. The short answer is that languages matter much less than they used to, but programming matters more than ever. The long answer requires taking a step back to understand what a programming language is really for.
What a programming language is for
A processor does not understand C. It does not understand Python, PHP or Java. It understands one thing only: sequences of bytes corresponding to elementary instructions, such as add these two registers or jump to this address if the result is less than or equal. Everything else is translation.
A programming language is a compromise between two parties: the human being, who reasons in intentions ("I want the sum of the numbers from 1 to 10"), and the machine, which reasons in instructions. Each language moves the meeting point a little closer to one side or the other.
Let's look at the same intention, adding up the numbers from 1 to 10, expressed at different levels.
Machine code (x86-64). This is what the CPU actually runs. Sixteen bytes:
31 C0 B9 01 00 00 00 01 C8 FF C1 83 F9 0A 7E F7
Assembly. The same bytes, with a mnemonic name in place of each instruction. It is a one-to-one mapping: whoever writes assembly is still speaking the processor's language, just with readable words.
xor eax, eax ; total = 0
mov ecx, 1 ; i = 1
again:
add eax, ecx ; total += i
inc ecx ; i++
cmp ecx, 10
jle again ; if i <= 10, repeat
C. The concepts of variable and loop appear. The compiler decides which registers to use and which instructions to generate. But the programmer still thinks like the machine: a counter, an accumulator, a comparison.
int total = 0;
for (int i = 1; i <= 10; i++) {
total += i;
}
PHP or Python. There is no explicit loop anymore. We describe what we want (the sum of a range), not how to get it.
$total = array_sum(range(1, 10));
total = sum(range(1, 11))
Natural language. The last rung, the one that was science fiction until a few years ago:
Add up the numbers from 1 to 10.
Five ways of saying the same thing. Going down, each level is closer to the machine: faster, more controllable, and more tiring to write. Going up, each level is closer to the human: more expressive, and hiding more and more.
The whole history of programming languages is the history of this ladder: every generation has added a rung at the top.
Seventy years of rungs
Every major language was born to solve a specific problem. Almost always, that problem was: there is something the programmer should no longer be forced to handle by hand.
| Year | Language | What it took off the programmer's shoulders |
|---|---|---|
| 1940s | Machine code | — programming meant switches, cables and punched cards |
| 1949 | Assembly | remembering the numeric codes of instructions |
| 1957 | Fortran | knowing the processor: you write formulas, not instructions |
| 1959 | COBOL | mathematical notation: English-like statements, for business software |
| 1972 | C | rewriting everything for every machine: code becomes portable |
| 1985 | C++ | organizing huge programs by hand: classes and objects arrive |
| 1991 | Python | verbosity: code that reads almost like pseudocode |
| 1995 | Java, PHP, JavaScript | memory management, the web, the browser |
| 2000 | C# | integrating a whole ecosystem: language, runtime and libraries |
| 2015 | Rust | choosing between speed and memory safety |
| 2022 | LLMs | syntax itself |
The pattern repeats
When John Backus and his team at IBM presented Fortran in 1957, many programmers were skeptical. A compiler, they said, would never produce code as efficient as assembly hand-written by an expert. They were right, at first. Then compilers improved, and within a few years hand-written assembly became a niche.
The same scene has played out at every rung. C was considered too slow compared to assembly. Java's garbage collector was considered a luxury for people who couldn't manage memory. PHP and JavaScript were "toy languages", not for real programmers.
Each time the criticism held a grain of truth, and each time history went the same way: abstraction wins, because human time costs more than machine time. A modern processor executes billions of instructions per second. A programmer writes a few hundred lines a day. It pays to waste a little of the former to save the latter.
LLMs are simply the next rung. The highest so far, and the first where the language at the top of the ladder was designed by no one: it is the language we speak.
So, whatever happened to C?
Surprise: it is doing just fine. It hasn't disappeared, it has just moved.
Let's go back to the PHP line from before:
$total = array_sum(range(1, 10));
array_sum() and range() are not written in PHP. They are functions of the Zend Engine, the engine that runs PHP, and the Zend Engine is written in C. The same goes for Python: the standard interpreter is called CPython precisely because it is written in C. When you write sum(range(1, 11)), somewhere a for loop in C is doing exactly what we wrote by hand in the C example earlier.
And the list goes on:
- the Linux kernel, which runs most of the servers on the planet and every Android phone;
- SQLite, probably the most widely deployed database in the world, inside every smartphone and every browser;
- the firmware of routers, washing machines, car control units, medical devices;
- a good part of the tooling that runs LLMs themselves, where C and C++ squeeze every last instruction out of graphics cards.
C is not dead. It has become the floor. Every rung added above it has made writing it less necessary, but has not made its existence any less necessary. Quite the opposite: the higher you build, the more solid the foundations must be.
What has changed is who writes it. C used to be everyone's language, from business software to video games. Today it is the language of those who build the foundations: operating systems, database engines, runtimes, drivers, embedded systems. It is a narrower, more specialized trade, and a very well paid one.
For someone starting out today, this means something specific: they will probably never write C for a living. But every line they write, in any language, will end up running on top of C.
The last rung: the language we speak
In January 2023 Andrej Karpathy, one of the founders of OpenAI and former head of AI at Tesla, wrote a line that has been quoted ever since: "The hottest new programming language is English". It was a provocation, but it described the new rung well.
What has become almost useless
For decades a huge part of the job was syntactic memory. Remembering that in PHP strpos() wants the haystack first and then the needle, while in_array() wants the needle first and then the haystack. Remembering how to open a file in Java, how to make an HTTP request in C#, which library to use to read a CSV in Python.
Today an LLM knows all of this better than any programmer. It knows it in dozens of languages at once, and translates code from one to another in seconds. The value of knowing a language by heart has collapsed, just as the value of doing arithmetic in your head collapsed when the calculator arrived.
Anyone who introduces themselves today as a "PHP programmer" or a "Java developer" is defining their job by its least valuable part.
What hasn't changed at all
There is, however, a fundamental difference between an LLM and a compiler. A C compiler, given the same code, always produces the same result, and if the code is ambiguous it refuses to compile it. An LLM accepts any sentence, even an ambiguous one, and produces something. Something plausible. Not necessarily something right.
The problem is that natural language is terribly ambiguous. Take a trivial request:
Sort the users by name.
First name or last name? Ascending or descending? Does "Élodie" come before or after "Zeno"? Does case matter? What if two users have the same name? And if there are two million of them, do we sort them in memory or ask the database for them already sorted?
An LLM will answer all these questions on our behalf, silently, picking the most likely interpretation. Most of the time it will be fine. When it isn't, only someone who knew those questions existed will notice the mistake.
In 2002 Joel Spolsky formulated what he called The Law of Leaky Abstractions: every non-trivial abstraction, sooner or later, leaks the details it is supposed to hide. The garbage collector hides memory, until the application runs out of RAM. The ORM hides SQL, until a page runs a thousand queries. The LLM hides the code, until the code is wrong.
And when an abstraction leaks, you need someone who knows what lies underneath.
What to study, then
If syntax is worth less and less, where should you invest your time? The principle is simple: study what stays true in any language. What an LLM cannot decide for you, because it depends on the problem and not on the syntax.
How the machine works
You don't need to write C, but you do need to know what happens when your code runs. What memory is and why it runs out. Why reading from disk is thousands of times slower than reading from RAM, and why a network call is slower still. What it means for two requests to arrive at the same instant.
This is why writing a bit of C, even just for a few weeks, is still one of the best exercises a beginner can do. Not to use it, but to see the floor they will be walking on for their entire career.
Data structures and complexity
An LLM will happily write you a nested loop that compares every element with every other. On a hundred elements it is instantaneous. On a million, $O(n^2)$ means a trillion comparisons, and the server grinds to a halt. Recognizing at a glance the difference between $O(n^2)$ and $O(n \log n)$, and knowing when an array should be replaced by a map, is a skill that no evolution of languages has ever made obsolete.
Architecture
This is where the job really shifts. An LLM writes a function very well. It is much worse at deciding where that function should live, which components should know about each other and which shouldn't, where the boundary between business logic and the database lies, what will happen to the system in two years when the requirements have changed.
Architectural questions — how do I split the system, who is responsible for what, how do I handle errors, how do I scale, how do I test it — are identical in PHP, C# and Rust. Whoever can ask and answer them can program in any language, English included.
The shared foundations
HTTP, SQL, transactions, concurrency, security. Technologies that are decades old, on which almost all modern software rests. An LLM that generates a query without an index, or an endpoint vulnerable to SQL injection, produces code that works. Noticing that it is wrong requires knowing the fundamentals.
Reading code
The paradox is that in the age of LLMs we write less code but read much more of it. The work shifts from writing to reviewing: understanding what a block of code you didn't write does, judging whether it is correct, writing the tests that prove it. It is the same skill as a senior doing code review for a junior. Except now the junior writes a thousand lines a minute.
The domain
Finally, the most underrated skill: understanding the problem. How an invoice, a booking, a warehouse, an insurance claim actually work. Code is just the translation of a model of the world, and a wrong model translated perfectly is still wrong. No LLM knows your client better than you do.
Conclusion
C is not dead. It has moved down a few floors and become the floor on which everything else rests. Every language that came after it added a rung to the ladder, and LLMs have added one so high that from the top you can barely see the bottom.
But the ladder is still there. And a programmer's job has never been knowing one rung by heart: it has always been knowing which rung you are standing on, what lies beneath it, and what happens when something breaks.
So to anyone who wants to start today, the advice is no longer learn C. Nor is it learn Python. It is learn to think like a programmer: break a problem down, model it, choose the right structures, predict where it will break. The language you express it in, whether PHP, Rust or English, is the least of the details.
For seventy years we learned to speak the machines' language. Now that they have learned ours, the hard part is no longer making ourselves understood. It is knowing what to say.