The history of software engineering can be understood as a history of abstraction.

The first programmers did not write assembly language. They wrote instructions in the language the machine itself understood, entering numerical codes into memory through switches, punched media, or other primitive interfaces. Assembly came later, providing names for those instructions and allowing humans to work with symbols rather than raw encodings. Compilers moved another layer away from the machine. High-level languages moved further still. Libraries and frameworks absorbed recurring structures. Today, increasingly capable models can translate natural-language descriptions into executable software.

The usual interpretation of this history is that programming languages became easier to use. That is true, but incomplete. What changed was not simply the difficulty of programming. What changed was where the difficulty lived.

Every new layer of abstraction removes some implementation burden from the programmer and transfers it to another layer of the system. The compiler assumes responsibility for machine instructions. The runtime assumes responsibility for memory and execution. Libraries assume responsibility for recurring functionality. Frameworks assume responsibility for architecture. Generative systems increasingly assume responsibility for writing the implementation itself.

The complexity does not disappear. It moves.

This distinction becomes increasingly important as software generation approaches a state where producing another implementation is extraordinarily cheap. The limiting problem may no longer be how quickly we can write software, but how quickly we can determine whether the software we have produced is correct, secure, maintainable, and worth having.

The history of abstraction therefore leads to a strange destination. The programmer may eventually spend less time constructing software and more time proving that the machine constructed the right thing.

1. Before the abstraction

The earliest electronic computers offered almost no distance between the programmer and the machine. A stored-program computer operates on encoded instructions. In the beginning, those instructions had to be represented in forms close to the machine's actual encoding. Programming could involve entering numerical instruction patterns, whether through switches, punched paper, or other input mechanisms. The programmer was responsible for knowing not merely what computation should occur, but how the particular machine represented that computation.

This was an extreme form of coupling. The programmer's conceptual model had to overlap with the machine's.

An addition was not simply:

a + b

It was a sequence of machine operations involving particular instructions, addresses, registers, memory locations, and representations.

The distinction between the computation and the representation of the computation was barely visible. Programming meant managing both.

The first great abstraction was therefore not a new algorithm. It was language itself.

2. The machine becomes symbolic

Assembly language introduced a small but profound separation. Instead of writing the numerical representation of an instruction, programmers could write a symbolic mnemonic:

ADD
LOAD
STORE
JUMP

An assembler translated those symbols into the machine's instruction encoding. The machine itself had not become more abstract. The interface between human and machine had.

The programmer no longer needed to remember that a particular binary pattern represented an addition instruction. The assembler took responsibility for that translation. A small portion of the programmer's work had been externalized into software, and this pattern would repeat for decades.

3. The compiler absorbs the machine

Assembly still required the programmer to think in terms of the machine. Registers remained visible. Memory remained visible. Instruction sequences remained visible. The architecture remained part of the programmer's mental model. The compiler changed the boundary.

Instead of specifying individual machine operations, programmers could begin describing computations in increasingly mathematical and human-oriented forms. FORTRAN was one of the decisive steps. The first FORTRAN compiler demonstrated that a higher-level representation could be translated into practical machine code without requiring programmers to manually construct the underlying instruction sequence.

Consider the difference:

load
multiply
add
store
jump
...

versus:

Y = A*X**2 + B*X + C

The second statement contains dramatically less information about the machine. That is precisely its advantage. The compiler is responsible for recovering the missing information. The programmer expresses the computation, and the compiler determines an implementation.

This establishes a pattern that would become fundamental to software engineering:

Abstraction is the delegation of implementation decisions to another layer.

4. The stack grows upward

Once compilers became capable of translating higher-level descriptions into efficient machine code, there was little reason for abstraction to stop.

Programming languages became richer. Operating systems abstracted hardware resources. Libraries abstracted common algorithms and functionality. Garbage collectors abstracted memory reclamation. Databases abstracted storage and retrieval. Networking stacks abstracted communication protocols. Frameworks abstracted application architecture. Cloud platforms abstracted physical infrastructure.

Each layer followed the same pattern. The programmer says less. The system underneath does more.

This can be represented as a sequence:

LayerWhat the programmer increasingly stops managing
Machine codeNothing below the machine representation
AssemblyRaw instruction encoding
Compiled languagesIndividual machine instructions
Operating systemsHardware resources
LibrariesRepeated implementations
FrameworksCommon application architecture
Cloud platformsPhysical infrastructure
AI coding systemsMuch of the source implementation

The remarkable thing is not that any individual abstraction exists. It is that the stack keeps growing. Software engineering has spent its entire history constructing machines whose purpose is to allow humans to think about fewer layers of the machine underneath them.

5. Abstraction does not destroy complexity

There is a temptation to interpret this history as a continual reduction in complexity. That interpretation is misleading. A programmer writing Python does not experience the complexity of a modern processor in the same way that an assembly programmer does, but the processor is not less complex. It is simply below the abstraction boundary.

The same is true of operating systems, databases, distributed systems, networking, compilers, and cloud infrastructure. Abstraction hides complexity from one participant by assigning responsibility for it to another component.

This suggests a useful principle:

Abstraction redistributes complexity rather than eliminating it.

The programmer writes less machine-level code because the compiler writes it. The application developer manages less memory because the runtime manages it. The developer writes fewer infrastructure configurations because the cloud platform manages them. The user writes less source code because an AI system generates it.

At every stage, someone or something still has to perform the underlying work. The location of that work changes.

6. The programmer moves upward

This redistribution also changes what it means to be a programmer. When machine code was the dominant interface, programming expertise required intimate knowledge of the machine. Assembly programmers needed to understand instruction sets. Compiler programmers needed to understand language semantics and machine architecture. Application programmers increasingly worked with data structures, algorithms, APIs, and abstractions. Modern software engineers may spend more time reasoning about systems than individual instructions.

The abstraction boundary therefore does something deeper than make programming convenient. It changes the level at which human intelligence is applied.

The programmer moves upward, and the machine takes over the lower layer. Then the programmer moves upward again, and another layer is automated. Then upward again.

Machine implementation
        ↓
Human abstraction
        ↓
Machine automation
        ↓
Higher-level human abstraction
        ↓
Machine automation
        ↓
Higher-level human abstraction

Software engineering is consequently not simply a process of humans becoming better at programming machines. It is a process of humans constructing increasingly sophisticated machines that absorb parts of programming itself.

7. Natural language is another boundary

Generative AI introduces a qualitatively interesting version of the same process. The programmer can now describe a desired system in natural language:

Build a service that accepts these inputs,
performs these operations,
persists the results,
and exposes them through an API.

A model can translate this description into code, and the programmer no longer necessarily specifies the implementation directly.

Intent
   ↓
Natural language
   ↓
AI model
   ↓
Source code
   ↓
Compiler
   ↓
Machine code
   ↓
Execution

Notice what has happened. The compiler once separated the programmer from machine code. The AI system now begins separating the programmer from source code itself. This is another abstraction boundary, but it is a fundamentally less rigid one.

Programming languages have formal syntax and defined semantics. Natural language does not. A compiler given a valid program can be expected to follow the language's rules. A generative model given a natural-language specification must infer what the specification means. That difference changes the engineering problem.

8. When generation becomes cheap

If AI can generate software rapidly, then the cost of producing another implementation begins to fall, but this does not imply that software engineering becomes free.

A generated program still needs to work. It still needs to integrate with existing systems. It still needs to be maintained. It still needs to resist attackers. It still needs to handle cases that were never described in the original prompt. It still needs to satisfy the actual requirements rather than merely produce plausible-looking code.

The result is an asymmetry:

The cost of generating software can fall much faster than the cost of understanding software.

The implementation becomes cheap. The confidence becomes expensive. Generation and verification are different problems.

9. The verification frontier

Traditional software engineering already contains an enormous verification apparatus, from tests, type systems, static analyzers, and linters to code review, formal methods, fuzzing, property-based testing, runtime monitoring, and security analysis. These mechanisms exist because writing software and establishing its correctness are different activities, and AI makes this distinction harder to ignore.

If a human spends eight hours writing a piece of software, spending two hours reviewing it may seem reasonable. If a machine generates a comparable implementation in seconds, the economics change. The implementation becomes cheap. The confidence becomes expensive.

The engineer increasingly becomes responsible for specification, evaluation, verification, architecture, security, and judgment.

10. The specification problem

There is an even deeper problem. Verification requires something to verify against, and that means we need a specification. But natural language is not a perfect specification language.

Consider:

"Build a secure authentication system."

What does secure mean? Against which adversaries? Under which threat model? What are the acceptable failure modes? What happens when credentials are compromised? How should recovery work? What are the latency requirements? What constitutes authorization? What data may be retained?

The AI can generate an implementation, but it cannot eliminate the ambiguity contained in the request. This reveals another consequence of abstraction. As implementation becomes increasingly automated, specification becomes increasingly important. The human moves further away from describing how the machine should operate and closer to describing what the machine should mean. That is a much harder problem.

11. The redistribution of effort

The history can therefore be understood not as a simple reduction in programming difficulty, but as a redistribution of engineering effort.

EraHuman emphasis
Machine codeEncoding and implementation
AssemblyInstruction sequencing
Compiled languagesAlgorithms and program structure
High-level languagesAbstraction and architecture
FrameworksSystem composition
AI generationSpecification and orchestration
AI + verificationCorrectness, security, and judgment

The total amount of complexity in the system does not necessarily decline. Instead, the distribution of responsibility changes. This distinction matters because it explains why software can become easier to generate while remaining difficult to engineer.

A modern web application can be generated in minutes, but that does not mean its architecture is sound. A thousand lines of code can be produced instantly, but that does not mean the system has been verified. An AI can implement a feature, but that does not mean the feature belongs in the system. Abstraction lowers the cost of certain decisions while making other decisions more important.

12. The strange economics of abstraction

This is where software engineering intersects with the broader question of marginal cost. Every abstraction layer attempts to make some previously expensive activity cheaper. The assembler made machine-code production cheaper. The compiler made machine-level implementation cheaper. Libraries made recurring implementation cheaper. Frameworks made entire classes of applications cheaper. Cloud platforms made infrastructure deployment cheaper. Generative AI makes portions of software construction cheaper.

The pattern is therefore not simply:

More abstraction means less work.

It is:

More abstraction means less work at the current layer and more responsibility at the layers above it.

This is why the approach toward marginal zero should not be interpreted as the approach toward zero engineering. It is the approach toward zero marginal effort in particular forms of production. The scarce work migrates.

13. The machine that builds machines

There is an irony at the end of this trajectory. The first programmers wrote instructions for machines. Then programmers wrote languages that generated instructions. Then programmers wrote systems that generated programs. Now we are beginning to build systems that can generate software from descriptions of desired behavior. The machine is progressively absorbing the machinery of its own construction.

Humans build machines
        ↓
Machines build abstractions
        ↓
Abstractions build software
        ↓
Software builds software
        ↓
AI builds software
        ↓
AI-assisted systems build increasingly capable systems

The programmer is no longer necessarily the person who writes every instruction. The programmer becomes increasingly the person who defines the conditions under which instructions are produced. That is a profound change in the role of software engineering.

14. The new bottleneck

If this trajectory continues, the bottleneck will not necessarily be code. It may be confidence. We may eventually have systems capable of producing millions of implementations for a problem before a human could write one.

The limiting question then becomes:

Which implementation should we trust?

And after that:

How do we know?

And after that:

What exactly did we ask the machine to build?

And finally:

Was it the thing we actually wanted?

The hierarchy becomes:

Generation
    ↓
Testing
    ↓
Verification
    ↓
Specification
    ↓
Judgment

The further downward we move toward automated generation, the more valuable the layers above it become. This is not a contradiction. It is the natural consequence of abstraction.

15. Conclusion: The machine moves upward

Software engineering began close to the machine. The programmer once had to know the instruction encoding, the memory locations, and the architecture. Assembly created distance. Compilers created more distance. High-level languages created more. Libraries and frameworks created more. Modern AI systems are beginning to create distance from the source code itself.

Each transition moves the human one layer upward, but the complexity does not vanish. It migrates. The machine assumes responsibility for another layer of implementation, while the human assumes responsibility for a higher layer of abstraction.

This is why the future of software engineering should not be described simply as the disappearance of programmers. The more interesting possibility is the disappearance of programming as the primary human activity. The engineer may increasingly specify systems rather than implement them. They may evaluate rather than construct, verify rather than translate, and decide rather than encode.

The machine writes the program. The human determines whether there should be a program, what it should accomplish, and whether the machine has actually built it correctly. That is the direction of greater abstraction.

And perhaps this is the deeper pattern behind technological progress itself. We do not merely build machines that do more work. We build machines that allow us to stop thinking about the work at the level where it used to be performed. The abstraction boundary rises. The machine moves underneath it. And humans move upward.

Notes

  1. Computer History Museum, "Software & Languages," Timeline of Computer History.
  2. Computer History Museum, historical accounts of early stored-program computing and EDSAC.
  3. John Backus et al., "The FORTRAN Automatic Coding System," 1957.
  4. David E. C. Davies and others, historical accounts of early assembly and stored-program computing.
  5. Marcos Cramer and Lucian McIntyre, "Verifying LLM-Generated Code in the Context of Software Verification with Ada/SPARK," 2025.
  6. Aaron Councilman et al., "Towards Formal Verification of LLM-Generated Code from Natural Language Prompts," 2025.
End of essay ← Return to home