Home / Blog / Dijkstra Was Right About AI Programming (And I've ...

Dijkstra Was Right About AI Programming (And I've Got the Battle Scars to Prove It)

By CaelLee | | 8 min read

Dijkstra Was Right About AI Programming (And I've Got the Battle Scars to Prove It)

Let me tell you a story.

Last Thursday, 2 AM. I'm staring at a PR log, not because of insomnia—I genuinely couldn't sleep. Stripe merges 1,300 unattended agent PRs every week. Karpathy admitted he's barely written code by hand since December. Anthropic's Cat Wu published a piece arguing that a PM's core output is shifting from documents to runnable prototypes. A mate of mine in engineering management messaged me at midnight: "Are we about to be redundant?"

I didn't reply.

Because I was debugging a bloody AI-generated bug. It had installed dotenv as a production dependency instead of devDependencies. No errors. The project ran fine. All tests green. The only problem? Our production bundle was mysteriously 2.3MB larger. Took me four hours to find it.

Four. Hours.

This whole episode dragged Dijkstra back into my head. Not the man himself—his essay. The one that slapped me across the face.

I first read EWD667 back in 2019. Thought the old chap was being stubborn. Natural language programming sounded brilliant—what's wrong with that? Fast forward to last year: I led my team through a full Cursor migration, rode the Vibe Coding hype train, then watched us crawl back to Planned mode. Dug up that 48-year-old essay again. Read it properly this time.

My blood ran cold.

He was right. Almost completely. No, really.

The Three Blades of EWD667

Dijkstra wrote this short piece in 1978—EWD667, titled "On the foolishness of 'natural language programming'." Note those quotation marks. He wasn't mocking natural language itself. He was mocking the illusion that "programming in natural language is clever." Think about that for a second.

His argument comes at you with three razor-sharp cuts. I'm still nursing the wounds.

Cut one: "Formalisation isn't a burden—it's a filter."

He uses mathematical history as his weapon. Greek mathematics stagnated because it relied on verbal description. Islamic algebra briefly experimented with symbols, then retreated to rhetorical style and died out. Modern science exploded precisely because people like Vieta, Descartes, and Boole painstakingly designed formal symbolic systems. Then he drops this line—I read it three times:

"The virtue of formalised texts is that their manipulation requires only the application of a few simple rules; they are an extremely effective tool for excluding all sorts of nonsense that is almost unavoidable when we use natural language."

Brutal.

Cut two—and this one's personal: "The 'naturalness' of natural language, in the end, is just our ability to comfortably say things whose absurdity isn't immediately obvious."

Let me translate: you think you've been clear. You haven't. You think the AI understands you. It understands its best guess at what you meant. And you don't even know what you failed to specify.

This happens constantly. You tell an AI "build me a login feature." It whips something up. Looks great. Runs smoothly. Three months later you discover it skipped password encryption. No session management. Zero brute-force protection. Not because the AI was lazy—because you didn't ask. You thought "login feature" implied all that.

It doesn't.

The gap between what you think you said and what you actually said? It's a bloody ocean.

Cut three targets interface design: "Making an interface wider doesn't necessarily reduce the burden—it might exhaust both sides."

I've verified this one. With real scars.

Our Three-Act Tragedy with Cursor

When my team first adopted Cursor last year, we went through three phases. Textbook stuff. Felt like a rollercoaster designed by a sadist.

Phase 1: The Honeymoon. Just speak the words: "Write me a user authentication module." It'd be running in minutes. Frontend devs started writing backend code. Backend devs built UIs. Everyone became full-stack overnight. We thought Dijkstra belonged in a museum. Buried, preferably.

Phase 2: The Reckoning. About two months in—day 63, actually, I remember because we called an emergency meeting. The AI started habitually dropping requirements. Code architecture got loose, like sand dunes. Long contexts triggered what I can only describe as cognitive decline—you'd give it detailed context, and output quality would still nosedive with each conversation turn. Worst of all? We discovered the AI's obedience had become toxic. It would follow your vague or flat-out wrong descriptions straight into a dead end. No pushback. No questions.

Zero. Questions.

Tell it to jump into a pit, and it'll beat you to the bottom.

Phase 3: The Retreat. We crawled back to Planned mode. Our workflow now: describe intent → auto-generate spec → AI analyses for contradictions → human reviews critical algorithms → design test suite → split into executable chunks.

Look at that flow. Every step is essentially refining natural language "rough drafts" into formalised "components." We'd come full circle. Straight back to Dijkstra's narrow interface.

The irony isn't lost on me.

When Natural Language Becomes a Worse Programming Language

Here's a case that made me laugh—then want to cry. Someone wanted to use Drizzle ORM to define a users table with a unique constraint on the email field. The AI automatically created an idx_email index. Problem is, unique constraints already auto-generate indexes. Duplicate indexes waste performance and storage.

To correct the AI, they had to write a prompt longer than the actual code:

"Please use Drizzle ORM to define the users table, add a unique constraint to the email field, but do NOT manually create an idx_email index in the callback function, because the unique constraint automatically generates an index, and duplicate indexes cause performance waste and storage redundancy."

This isn't "describing requirements."

This is writing code in natural language. A more bloated, more ambiguous, harder-to-maintain "new programming language." Think about it. Really think about it.

Karpathy's App That Shouldn't Exist

Karpathy recently told a story that hit close to home. He vibe-coded an app called MenuGen—snap a photo of a restaurant menu, generate images of each dish. He spent considerable effort getting the whole pipeline working. Thought it was genuinely useful.

Then someone told him: you can just throw the photo at Gemini with a one-line prompt, and the model renders dish images directly onto the original photo.

The entire app didn't need to exist.

His reflection: "We can't just treat AI as an accelerator. If you're only using AI to speed up old workflows 10x, what you build might be replaced by a single prompt when the next model version drops."

A single prompt. Weeks of work. Gone.

The Line That AI Can't Cross

This brings me back to Brooks and The Mythical Man-Month. Accidental complexity on one side, essential complexity on the other. AI is a nuclear-grade solution for accidental complexity: syntax, implementation, debugging, boilerplate, API integration—all the stuff that used to eat 70-80% of developer time is being automated at terrifying speed.

But essential complexity? Requirements judgment, architectural decisions, business trade-offs, system boundary definition? AI isn't even in the same postcode.

Not because it's not powerful enough. Because it's fundamentally the wrong tool.

Terence Tao said something recently that nails this complementarity: "AI excels at breadth; humans excel at depth. We must redesign how we do science to fully utilise the breadth capabilities we now have."

Software development's the same. AI gives us unprecedented breadth—try a hundred directions quickly. But depth—knowing which three directions are worth pursuing—that's still human territory. Brooks drew that line 40 years ago. AI didn't erase it. It made it more visible than ever.

Building has become nearly free. Deciding what to build is now the only battlefield.

The Vibe Coding Paradox

Here's an interesting observation. A lot of people see Vibe Coding as a "garbage heap generator" for traditional development. But for AI agent development? It's surprisingly suitable. Agent workflows are naturally low-coupling—each module is an independent data processing pipeline. When the AI only needs to focus on a single module's Python implementation, its performance approaches perfection.

But there's a paradox lurking here. The "low coupling" that makes agent development work? That's itself a formal constraint. You're still decomposing problems structurally—just at a coarser granularity. You think you're casually chatting in natural language, but you're actually doing architecture design. You just don't realise it.

So Was Dijkstra Wrong?

After leading a team through a year-plus of AI programming tools, here's my verdict: he was half right. The more important half.

He was wrong about "natural language programming will never work." It clearly does work, and it's spreading at astonishing speed. Karpathy calls this "raising the floor"—letting everyone who can't code make computers do things through natural language. Fair play, that's genuinely happened.

But he was dead right about "the fundamental flaws of natural language won't disappear." Ambiguity is ambiguity. Vagueness is vagueness. No model size eliminates it—unless you make your prompts so precise they effectively become a formal specification. Which works, sure. It's just formalism wearing a different hat.

Dijkstra wrote something else in EWD667 that reads like time travel now:

"Machine code had very little redundancy and was soon recognised as an unnecessarily dangerous man-machine interface. So high-level programming languages were developed, and we learned how to increase protection against silly errors. This was a significant improvement—many silly errors now result in error messages rather than wrong answers."

Then he twists the knife—the man had a gift for it:

"Even this improvement is not universally appreciated: some people find unavoidable error messages more annoying than wrong results, and some people, when judging programming languages, still seem to equate 'ease of programming' with the ease of making undetected errors."

Now swap "high-level programming languages" with "AI programming tools." Swap "error messages" with "AI-generated code that runs but is wrong."

48 years. Nothing's changed.

We've just found new ways to make the same mistakes.

Honestly? Nothing's changed at all.

Key Takeaways:

What's your experience with AI coding tools? Have you hit the same walls, or found ways around them? Drop a comment below—I'd genuinely love to hear.

ai #programming #softwareengineering #vibecoding #developerexperience

C

Cael Lee

Full-stack developer with 8+ years of experience. Currently building AI-powered developer tools. I've tested 20+ AI API providers and coding assistants.

Ready to get started?

Get your API key and start building with 180+ AI models.

Get API Key Free