It Happened Overnight
When a programming skill becomes machinery, and what that suggests about source code.
There is a particular feeling you get when you return to a piece of technology you once knew intimately and realise that most people no longer need to know it exists. It is not the same as discovering that a better version has been released. The work has moved. The thing you spent so much time learning is still going on somewhere, but it has stopped being your job.
I have been programming for nearly thirty years, and I have seen that happen before. I believe I am watching it happen again, this time to something much larger.
When I say coding is becoming a solved problem, I am not saying every program can now be produced correctly on demand. I am making a claim about where the human stands in relation to the machinery. A problem can be very much alive inside the machine and largely finished as a routine occupation for the person using it.
That distinction is the reason for this chapter.
The work I remember
Our project was a C++ application, built in the very early days of Microsoft Visual C++, before the compilers were good. Making the whole thing work meant building the linker inputs by hand — the scripts and map files you deciphered when the result was wrong — and knowing exactly what each tool expected from you. Most desktop developers never saw this layer; on that project, you could not avoid it. There were the worlds of C and C++, with their own habits and traps. There were tools such as Bison and Flex, from a different part of building software but carrying the same unmistakable flavour: a machine could generate something useful, provided you learned the particular language and procedure by which it wanted to be addressed.
I remember how normal this all seemed. The machinery was difficult, but difficulty was part of programming. You invested time in learning it. You recognised the reports. You developed a feel for where a problem lived. You became the person who could get the system to build when someone else was staring at an impenetrable page of output.
That was real expertise. It mattered because it stood between an intention and a running program.
Now consider the experience of many people writing ordinary applications today. They may never inspect a linker map. They may not know what a linker script looks like. They may use libraries containing parsers without knowing whether those parsers were generated or written by hand. They can still develop useful software. In many settings, their tools choose the defaults, run the build, report the actionable failure, and conceal the detail that once required specialist attention.
What changed? The problems did not cease to exist. The programmer’s relationship to them changed.
That is the part I want the reader to recognise. Not that old tools were bad and new tools are good. Not that every old skill has expired. But that something can go from the centre of your working day to the edge of your awareness while the software itself becomes more capable.
Three different pieces of machinery
It is worth being precise about the history because the distinction makes the argument stronger.
Flex is a lexical-analyser generator. It can take a description of patterns in text and produce a scanner. Bison is a parser generator. It can turn a grammar description into a parser. They are related tools, often used together, but they are not linkers.
Linking is another stage. The linker combines the pieces needed for an executable or library, resolves symbols, and arranges output according to the platform and build. A linker script can govern how input sections are mapped into the output and where things live in memory; a map file can expose what the linker did.
I group these memories together not because the tools do the same job, but because they illustrate what programming once asked the human to manage directly. They had specialised inputs, specialised outputs, specialised vocabulary. To build the thing you wanted, you sometimes had to learn the machinery between you and the thing.
The tools still exist. Indeed, they remain essential in particular contexts. A compiler writer, embedded developer or engineer working close to a platform may need to understand them very well.
Those disappearances happened by two different routes, and the difference matters. Linker work was absorbed by automation: build systems and development environments took the arrangements over, so the same work still happens, just not by hand. Parser writing mostly vanished a different way, through packaging and specialisation: most developers stopped writing parsers at all, because standard formats, libraries, and ready-made components removed the need — the direction even reversed when GCC replaced its YACC-derived C++ parser with a hand-written recursive-descent one in version 3.4, as recorded in that release’s notes. AI is a third mechanism, unlike either: general-purpose translation that takes on the work without the problem first being standardised. That is why it can move faster and further than both earlier shifts — and why it deserves its own argument rather than borrowing theirs.
And there is an interesting consequence. A developer who never learned those older details is not necessarily a worse developer. They may be spending their attention on problems that the earlier generation could barely reach. They are standing on the work of people who made the older machinery dependable enough to stop thinking about.
This is how technological amplification often feels from the inside. For a while you celebrate each improvement. Then one day you realise a whole category of work has fallen out of your vocabulary.
It happened overnight
I say it happened overnight, and I mean something specific by it. The tools improved gradually — year by year, release by release. But delegation is binary: at any given moment you either still do the thing yourself or you do not. There comes a project where you simply stop building the linker inputs by hand, because the environment handles it; a Tuesday when the map file stops being a document you read and becomes one you never open. The crossing can be sudden for the person living it even when the technology crept up on it for years. That suddenness is what I mean by overnight — not a claim about the history of compilers, but about the experience of the threshold. There is a point at which the old task stops coming up. The next time you begin a project, the build system already handles it. A newer environment gives you an answer instead of a map file. Something you formerly did by hand is now an implementation detail. You have crossed a line, sometimes without noticing the moment you crossed it. Thresholds, unlike trends, are decisions you can notice — which also means you can miss them, and most of us, busy with the machinery of the day, miss them until something forces the comparison.
If you spend your working life learning to operate machinery, that moment can be disconcerting. You have invested skill in something that no longer buys you the same advantage. You can continue doing it, and in some places you should. But for much of your work, insisting on doing it yourself is no longer evidence of mastery. It may simply be a refusal to use the tools now available.
This is where my view of machine coding begins.
For a long time, programming meant translating intention into source code, and source code into something the computer could execute. We built wonderful tools to make the second translation easier. Now a different kind of tool is taking over increasingly large parts of the first translation as well. The useful question about AI is not whether models are improving — they are, gradually. It is whether a particular task has crossed your threshold: the point where doing it yourself stops being worth it and directing the machine takes over. For writing ordinary implementation, that crossing is already behind many of us; for verifying it, the threshold is further off and still moving.
The change has not completed. It is certainly not equally advanced in every domain. But the experience of shifting responsibility is familiar to me. I have seen an essential-looking part of programming turn into machinery before.
The difficulty is recognising the transition while you are still living in it.
Solved does not mean vanished
We are used to treating a solved problem as a problem that has gone away. That is not how engineering works.
Consider the everyday act of saving a file. Enormously difficult problems have been solved, and are continually being solved, beneath that operation: storage controllers, filesystems, caches, recovery mechanisms, networks, permissions, hardware reliability. Most people do not need to operate those mechanisms to save a document. Specialists still do. Failures still occur. The work continues.
For the ordinary user, much of that complexity is sufficiently handled that they can stop thinking about it most of the time.
This is the sense in which I use the word solved.
A problem is solved at a particular level of abstraction when it can be delegated reliably enough that the person working at that level does not need to perform it routinely. Solved for one class of tasks is not solved for every task. Solved for a prototype is not necessarily solved for a hospital. Solved for a consumer application is not necessarily solved for a low-level device driver.
The qualification matters because it prevents us arguing about the wrong thing.
Whenever I say coding is becoming solved, somebody can point to a model that introduced a bug or a project that collapsed under generated complexity. They are not wrong. Those are real failures. But they do not establish that the human must permanently resume writing every line. They establish that the machinery, the method of directing it, and the ways of evaluating it still need work.
The existence of a failure is not proof that an abstraction is invalid. It tells us where that abstraction is not yet dependable.
The interesting question is how quickly the boundary moves: which categories of implementation can now be entrusted to a machine under suitable constraints and evidence, and which still demand specialised human involvement? I expect the first category to grow. That is a prediction informed by my experience, not a claim that the final boundary is known.
There is no shame in remaining a specialist underneath an abstraction. There is, however, a cost to assuming that every person must keep performing work that most applications no longer need them to perform.
Will code become like the linker file?
This is the uncomfortable comparison I want to make.
I believe that much of the source code people now write, navigate and inspect may become like the linker material I remember: essential to the system but no longer the thing a typical developer spends their day manipulating.
It will still be there. It may still be readable. It may still need examination when the system behaves unexpectedly, when safety demands it, or when a specialist wants to optimise an important detail. But the normal interface through which a person creates software may shift away from source code itself.
A person describes an intention. The machine produces an implementation. The person uses what was produced and describes the mismatch. The machine changes the implementation, runs tools, checks assumptions, and presents the new result. When a particular property matters, the person asks for evidence and may employ other machines to obtain it.
At no point does the source code have to become the primary conversation.
I am not proposing a new language in which the programmer has learned to hide the old syntax behind prettier prompts. I am proposing a change in what programming is. The person no longer needs to be the one translating every decision into instructions. The machine increasingly carries that translation.
Those activities — finding the intention, organising work, checking the result — are necessary today, but they are not a new collection of permanent clerical duties. More and more of them can themselves become machine work.
If you have spent years learning to read code, this can sound like an attack on your competence. It is not. Your competence may help you recognise risk, structure a difficult problem, or see why a beautiful demonstration is actually wrong. The change is in where you apply it.
You can keep your experience without insisting on performing the same operations forever.
The serious objection
There is a good objection to my analogy, and I do not want to hide from it.
A parser generator takes a constrained description and produces a particular kind of program. Linkers work within defined file formats, symbol rules and platform conventions. We can examine their behaviour in terms of explicit contracts. They are not usually deciding what the user meant by a vague request.
An AI coding agent is different. It may misunderstand an intention. It may modify more than requested. It may invent a reason, omit a step, misunderstand the system, or confidently report a result it did not establish. Its output can be plausible even when it is wrong.
So it would be foolish to argue that because build machinery became routine, AI-generated code must become equally dependable on the same timetable.
That argument was never required. There are two precedents worth keeping separate.
What I see instead is a growing system built around an imperfect generative component. The model is only one part of the machine. The rest can include repository context, tools, tests, execution environments, independent review, constraints, recorded decisions, and direct experience of the product. Those layers are the practical engineering of making delegated implementation useful. And the precedents for trusting such a system are older than AI.
The first precedent is the team. We already delegate implementation to unreliable minds — colleagues, contractors, juniors — without demanding that they be deterministic first. Nobody asks a new hire to prove parser-like reliability before letting them commit; we review their work, check what matters, and extend trust as the evidence accumulates. AI deserves the same treatment, not a stricter one. The right question was never whether the implementer is infallible. It is whether the surrounding system catches what matters.
The second precedent is the compiler itself — but not as the determinism story is usually told. Early optimising compilers were not oracles; they earned trust the slow way, release after release, by working. Programmers did not trust them because they were provably correct. They trusted them because, over time, the evidence said they could. That is the route AI is on: not a claim that stochastic tools will match a deterministic timetable, but that dependability, wherever it comes from, is established by observation rather than assumed from architecture.
So the objection gets things backwards. The issue was never whether the machine is as dependable as a linker. It is whether the human must personally perform work the surrounding system already handles well enough. I expect that boundary to keep moving, and I expect each move to be argued about exactly like this one.
The mistake would be to think that the presence of a human review requirement today proves that routine code review must remain the human’s principal activity tomorrow. That is another task the machine can increasingly help perform. Whether it does so reliably is an engineering question to be answered with evidence, not an article of faith.
I am arguing for a new default way of thinking, not for abandoning reality when reality disagrees.
What is becoming solved
Now we can state the claim in a form that is strong enough to matter and narrow enough to be useful.
For a growing range of software work, the difficult part is becoming less about writing the implementation personally and more about bringing a coherent result into existence through a sequence of directions, experiments and judgements.
The machine can write much of the code. It can search an unfamiliar repository. It can generate tests. It can investigate failures. It can propose alternative architectures and help integrate changes. The extent to which it can do these things well varies by model, runtime, task and environment; there is no universal guarantee.
But if I can achieve the desired result by directing those capabilities, then my inability—or unwillingness—to hand-write the code is no longer the decisive limitation it used to be.
This is not the same as saying engineering is solved. A working demonstration is not a dependable service. A technically correct application is not necessarily a good product. A useful prototype may become expensive to operate or impossible to secure. The gap between something that runs and something people can rely upon is real.
The question is which parts of closing that gap must be performed by the human. I think the answer will keep changing.
The most important change may be psychological. A programmer who starts by thinking about files, classes, methods and frameworks is already considering the solution in the vocabulary of implementation. That may sometimes be exactly right. But it is no longer always the best starting point.
I now prefer to begin with what I want to exist. What should someone be able to do? How should it behave? What would make me change my mind? What should never happen? How will I recognise when the result is good enough to use?
The machine may respond with a codebase, or with a prototype, or with questions I had not considered. It may offer a different approach. I can try it, reject it, or deliberately change the intention.
This is not merely a faster way of reaching the same finished program. It changes which programs I am willing to attempt in the first place.
Stop polishing the old interface
A great deal of professional identity comes from knowing things other people do not know. Programmers have always had that experience. Languages, APIs, build systems, debuggers, platforms: we invested years learning the mechanisms. Those skills could earn us a living and the respect of people who needed them.
It is understandable to feel uncomfortable when a machine begins doing the work.
The new opportunity is to ask questions we once could not afford to ask.
What if I built a working version of this idea instead of arguing about it for weeks? What if I tried three different interfaces instead of committing to the first? What if the machine built a simulation so that I could see whether my theory survived contact with reality? What if the cost of discovering that an idea was wrong became small enough that being wrong early was an advantage?
This is where the story about the toolchain points.
When a layer of work becomes sufficiently handled, we gain the freedom to operate above it. The value of that freedom is not that we can do the old work without thinking. It is that we can attempt something else.
I do not know precisely how far this will go, or whether every field will make the transition in the same way. I expect many exceptions. I expect specialised engineers to remain essential in places. I expect the machinery to fail, sometimes badly, and to require care when the stakes are high. I also expect what looks like an exception today to become ordinary machinery in some future workflow.
The book does not need to win a prediction contest about the exact year this happens. It needs to teach the reader to recognise the shift and use the capabilities already available.
For me, the strongest evidence of a change in programming is not that a machine can produce thousands of lines of code. It is the moment I stop beginning with code at all.
I begin with an idea. I talk it through. I ask the machine to build enough of it that I can find out whether the idea is any good. And once I have something in front of me, I can see things I could not see while it existed only in my head.
That is the next step.
Not write a perfect specification, then implement it. Not spend months deciding which idea would work. Build to find out.