Do we still need engineers now that AI can write code?
Part 1 of a three-part series on AI, engineering, and what actually changed.
It’s the question sitting under every conversation about AI and software right now, usually unspoken and slightly anxious: if the machine can write the code, what exactly is the engineer for? Boards are asking it in headcount meetings. Engineers are asking it at 2am. And the honest answer starts by noticing that the question contains a hidden assumption — that writing code was the job.
It wasn’t. The code was never the hard part. The thinking was. What separates an engineer from a non-engineer with the same model in front of them isn’t typing speed — it’s who is holding the architecture in their head, and whether they can tell when the machine has quietly walked off with it.
Let me build that out, because it turns on how these tools actually behave.
LLMs optimise for local plausibility, not global coherence
An LLM writing code sees a slice of the codebase, pattern-matches to what looks normal in that slice, and produces a change that satisfies the immediate criteria: it compiles, it answers the prompt, it passes the tests, the dependencies resolve. That is a genuinely useful thing to be good at. But notice what it is not.
It has no durable model of the architecture. It has no memory of an abstraction you built three months ago and the three reasons you built it that way. And crucially, it has no incentive to refactor, because refactoring enlarges the diff and enlarges the risk of breaking something — and the model is rewarded for a clean, contained, plausible change right here, right now.
Left to its own devices, the agent therefore produces a predictable set of pathologies:
- Duplicated APIs — it doesn’t remember that this capability already exists elsewhere, so it builds a second one.
- Surgical patches that accrete into mud — each individual change is locally reasonable; the sum is a swamp.
- Architecture that is emergent rather than intended — a shape nobody chose, that simply fell out of a thousand local decisions.
The deeper issue is that architecture is a set of constraints held in someone’s head — or, ideally, written down. If those constraints aren’t captured somewhere the AI can actually see, it infers them from the code. And the code is a lossy, contradictory record of every past decision, including all the bad ones and the half-finished migrations. So the model faithfully reproduces the inconsistencies. It has no way to know which patterns in your codebase are the intended architecture and which are the scar tissue.
Greenfield is arguably worse, not better
You’d think a blank slate would be safer — no legacy mud to imitate. It’s the opposite.
With no existing code to infer the architecture from, the agent falls back on population averages from its training distribution: whatever is fashionable and common on GitHub. Implicitly inferring an architecture from an existing codebase is bad, but at least it’s anchored — it’s grounded in decisions someone once made for reasons. Making architectural decisions by sampling the probability distribution of public code is worse in a specific way: it’s invisible. It’s an average of GitHub masquerading as a choice. There’s no rationale, because no one decided anything. No one weighed the trade-offs for your context, your constraints, your cross-functional requirements.
And it compounds. Multiple sessions, re-prompting from scratch, “just regenerate it” — each pass can produce a locally coherent design, but the passes don’t agree with each other. You end up with several internally-consistent-but-mutually-incompatible architectures coexisting in the same project, like a building where each floor was designed by someone who never saw the others.
So what does the engineer actually add?
This is the crux, and it’s the real answer to “do we still need them.” An engineer brings things the model structurally cannot:
Spotting the inconsistencies: Identifying inappropriate architectural decisions or hidden code smell, rather than accepting whatever happens to compile.
Holding a target architecture: Maintaining a long-term architectural vision based on cross-functional requirements for this specific context and steering the agent back whenever it begins to drift.
Designing constraints before syntax: Planning and bounding implementations up front, rather than letting the model free-associate across the codebase.
Recognising the classic failure modes: Spotting the structural gotchas that pass local tests but fail under production load—missing auth checks, unindexed queries, swallowed exceptions, or race conditions.
Working inside scaffolding: Version control, CI/CD, infrastructure as code, observability, targeting a specific deployment configuration and environment. This is the quiet one, and it’s where the real risk lives — because it’s exactly the scaffolding that non-engineers tend to work entirely outside of. (That’s the subject of the next post.)
So, do we still need engineers?
Yes — but not for the reason the job description used to imply. Not as the people who type the code, which is the part the machine is now genuinely good at, but as the people who hold everything the machine can’t: the architecture, the constraints, the judgement about what’s scar tissue and what’s intentional, the recognition of the failure mode that passed all the tests and will page you at 3am anyway.
The uncomfortable version, if you’re an engineer: the part of your job that felt like the job — producing code — is the part being automated. The part that felt like overhead — the thinking, the deciding, the holding-it-together — is the part that turns out to be irreplaceable. The tools haven’t made engineers obsolete. They’ve made it brutally clear which part of engineering was ever the point.
Next in the series: if the hard part is holding the architecture, what happens when someone who was never taught to — a non-engineer with an AI agent — builds software anyway? That’s where “vibe coding” earns its name.