Foreword: I’ve tried to make sure the numbers below are correct. However, if I (say) miscounted the number of instructions in the Z80 instruction set, please don’t feel obliged to let me know, as the precise numbers aren’t the point.
A lot of people are talking about whether we should use generative AI to generate code. I want to talk about the valid reasons why people want to use it in the first place, because I see generative AI use as primarily a symptom of a much deeper problem.
Making the easy things hard
People often say that yes, genAI is bad for all the reasons you’ve already heard, but it’s good for generating tedious boilerplate.
As someone who spent years writing Java, I’m certainly familiar with what tedious boilerplate looks like. Once you need to (say) access a database, things get ugly quickly. That’s why the pain of writing SQL plus JDBC wrapper code led to the creation of the Java Persistence API. JPA lets you avoid SQL entirely, and fetch data from the database in a typesafe way. Assuming you have the dependencies set up in Maven via a few dozen lines of XML, you can create a Session from a SessionFactory, create a CriteriaBuilder for the session, create a CriteriaQuery from that, create Expression objects from the criteria builder, apply them to the query, pass that to the session, and get the list of results. Repeat for every database-querying function. And that’s why people want to use LLMs.
All the library code doesn’t come for free, of course. A few years ago I tried building a “hello world” web application in Jakarta EE using the lightest available application server, and it needed 160 MiB of RAM to run. You’re probably thinking 160 MiB is nothing, and that’s a problem too — our ideas of what is a reasonable amount of resource usage are in need of adjustment now that disk space and RAM are no longer cheap.
The semantic complexity of modern programming languages themselves is also an issue. A couple of decades ago I saw an estimate that it takes 5 years to become a proficient C++ developer, and I expect it’s significantly harder now. While it’s an imperfect measure of complexity, the latest C++ standard has 92 keywords and 34 operators, and the actual language standard is a document with 1,853 pages. Meanwhile, JavaScript has fewer keywords at 46, but 44 operators, and the behavior encoded is so complex that it has become a running joke. Its standard is about as many pages as C, around 800.
New languages aren’t necessarily any better; Rust seems to be aimed at people who think C++ is great apart from the lack of memory safety, and it has 56 operators and at least half a dozen types of string. Is it any surprise that the Rust community loves using chatbots to deal with all that syntax and the corresponding semantics?
Yet in spite of modern programming languages being packed with features, conceptually trivial tasks can require ludicrous amounts of code. Consider formatting a date in RFC2822 format in JavaScript, for example. This leads to projects relying on libraries to ease the pain, and those libraries rely on libraries to ease their pain, and before long you have a trivial JavaScript web application with over 13,000 dependencies.
Very few programming languages make any attempt to avoid complexity; in fact, the trend is generally in the opposite direction. I used to write Perl code to solve problems in a quick and dirty fashion, but I was never really completely happy with it. Then Larry Wall announced that Perl 6 was going to happen, and I thought at last it would be a chance to clean things up. Maybe we could have one way to define objects rather than 4 different OO options in CPAN? Then I saw an early version of the Periodic Table of the Operators for Perl 6, and realized nothing was going to improve. I ran away to Ruby, where everything is an object, there’s one syntax for defining objects, and operators are just optional syntactic sugar for methods.
It’s difficult to avoid having your programming language get complex. People get angry at the Go team because of the simplicity of the language. Why doesn’t it have optionals and unions? Where’s the macro preprocessor? Can we add checked exceptions? Why can’t we control exactly when the GC runs? Can’t the compiler just infer argument types? It must be tempting to just add everything people ask for so that they’ll stop complaining, but if you do that then you end up with C++ again.
Features should not be a goal when developing a programming language. Making it able to solve problems should be the goal. Adding features is a necessary evil towards that goal. But egotistical developers love complex programming languages where they can show off how clever they are, so there’s always a community out there requesting more features.
Code is also not the goal of programming. Generally the less code the better, as every line of code is an opportunity for a bug to hide. The best commits are the ones that remove code, and the ideal amount of code is no code at all. Code quantity should be reduced down to the point at which it starts to become unclear or fails to solve the problem.
Kotlin is an example of a language I initially liked but moved away from. It has one killer feature — it solves Java’s NullPointerException problem. Unfortunately I found myself writing clever and terse code, rather than clear code. Writing clever code is certainly fun; there’s definitely something satisfying about packing the largest amount of computation into the smallest possible amount of code. It’s a logic puzzle, a game. It’s also terrible for code maintainability. But verbosity is bad too; those years writing Java taught me that. A final cluttering every method parameter, repeated catching and throwing of exceptions, String and StringBuffer conversions everywhere.
What is needed is code simplicity. That’s not the same as conciseness, and I don’t think anyone has really worked out how to measure it. It’s an instinct you get with years of programming — I hope. Reviewing code, I had a rule of thumb that if I had to look at a few lines of code for more than about 10 seconds, the code wasn’t clear and simple enough. How would I automate that? I’ve no idea.
Works for me
Once you’ve written your software, you have to worry about distributing it. With so many dependencies and so many components, for a lot of projects it’s no longer a matter of sending out a zip file. It doesn’t work? Well, it works on my machine, so I’ll just ship you the software setup from my machine in a container. Installation is a nightmare? I’ll ship it preinstalled in a container. It needs a database? Another container.
Seems like a win, except that studies suggest the average container contains code with over 600 known vulnerabilities, with nearly half of them being in code that’s more than 2 years old. Containers are basically collateralized technical debt obligations. And now you need a container manager to run the software in the containers, and maybe an orchestrator to make sure they run at the right times and connect to each other properly.
Or you could use a cloud platform to run the containers. AWS offers over 500 different services, so there are definitely ones that will help you run your application. Or there’s Azure, which has a few more. Naturally both of them have AI bots to help you work out what on earth is going on.
Doing what I want
Another reason people give for using GenAI, specifically for “vibe coding”, is that it lets them develop custom software for their own use — either personal use, or in their business.
Once upon a time we had deterministic tools for that. In the 8 bit era when you switched the computer on it typically dropped into BASIC in under a second. BASIC wasn’t a great programming language, but it was good enough that many people wrote their own bespoke business software — not to mention utilities and games. If you wanted to draw something on the screen, typically it was a handful of lines:
10 MODE 2
20 MOVE 200,100:DRAW 300,200:DRAW 100,200:PLOT 85,200,100
Now you have a square. Compare that with OpenGL as found at the start of the 2000s.
Later in the 80s we moved to 16 bit computers, then 32 bit. HyperCard let Mac users build their own mini databases, games, utilities and interactive systems mostly visually, by dragging and dropping text fields, buttons and other controls. In the 90s, Visual BASIC let Windows users do a lot of the same things. Meanwhile, Lotus Notes would let businesses build secure groupware by pasting controls on a page, and the result would feature digital signatures and encryption. You could build custom applications for your use in an afternoon. Sure, the results could be pretty ugly, but they worked, and were good enough — just like modern LLM-generated applications.
And now? Apple estimates that it takes over 16 hours to learn the basics of developing an application using Xcode. On the way you’ll need to learn about state management, events and lifecycles, and navigation hierarchies. Yes, you can get a lot further with Xcode than with BASIC, but most people aren’t going to spend half a dozen hours getting to “Hello, world”. They’ll give up before they get to the complex stuff.
To quote Alan Kay, “Simple things should be simple, complex things should be possible.” We seem to have lost the first part of that principle.
The bug factory
With projects built using languages as complex as C++ and JavaScript, with hundreds (or even thousands) of dependencies, the shape of the graph of CVEs by year isn’t a huge surprise.
A lot of CVEs aren’t remotely exploitable, and there’s significant CVSS inflation with reporters hyping up severity. At the same time, software maintainers are facing a deluge of low effort or outright bogus AI-generated vulnerability reports. Still, it looks like security issues are getting more frequent and more severe, even if you assume AI has increased the percentage that get found; and the trend started before AI got good at finding security issues.
It’s not just security issues either. Microsoft GitHub is now so unreliable that customers are starting to migrate away. One recent bug deleted code from over 2,000 projects.
The web duopoly
The median web page size is now 2.6 MB on mobile, 3 MB on desktop, spread over 70 separate requests. If you look at the amount of HTML, though — the text of the page, often the part you actually care about — that’s an average of 35 KB, meaning the page is 99% bloat. To pick a real example, I went to read the text of a news story on CNN. 19.7 MB downloaded and 52 tracking cookies, for 12.5 KB of article text. (And no cookie banner with opt-out, which means they’re breaking state law.)
The trackers and ads don’t help, but modern web development is part of the problem too. Once upon a time you’d refer to the HTML 2 spec (179 KB) and write your HTML; now you’ve got a 15 MB HTML 5 spec, plus a thousand pages of CSS specs. Not that most web developers write HTML — they’re all writing React and generating div soup. That is, instead of writing HTML and CSS, they write JavaScript, embed HTML in the JavaScript, import CSS into the JavaScript as modules, and if they want a bit of JavaScript on the page they embed JavaScript in the HTML that’s embedded in the JavaScript. Instead of updating the actual page via the browser’s object model, they update a separate virtual object model based on the real one, so React can work out the differences before and after, and then apply those differences to the actual page. It’s slower than just making the updates directly using JavaScript and native DocumentFragment and HTMLTemplateElement, and it uses more memory, but approximately nobody doing web development cares about that. It makes life easier, I’m told, and you only need to add 531 KB of bundled React runtime to your web page.
Parsing all the HTML, JavaScript and CSS on a modern web page, and running the JavaScript quickly, is a tricky task. Once you add in all the web APIs for things like background tasks, push notifications, and local database storage, it becomes a massive undertaking. As a result, we’re now at the point where the web is controlled by two corporations — Google and Apple — because they control the two rendering engines every popular browser uses. (Firefox is no longer a popular browser, Mozilla having managed to drive it down to 3.3% market share.) If Google and Apple decide everyone needs a device they’ve approved in order to access the web, then that’s what happens.
We’re going to need a reinforced desk
Desktop software is going the same route as web applications. In many cases, literally — many popular desktop applications are now built using Electron, bundling an entire web browser engine and developing the application in JavaScript, HTML and CSS. Sometimes a Node.js runtime is bundled as well. The result is that you can easily end up chewing up 2 GiB of RAM to edit a text file. Text chat via Discord can eat 1-4 GiB.
Microsoft Office also wants 4 GiB of RAM these days, but at least you get a full word processor. More than full, in fact. It now offers around 1,500 commands, with the average user touching fewer than 20 of them. People used to love Word, but now for many people the first draft of a document happens somewhere else, often in Markdown.
Word-style feature bloat isn’t a new problem. My first Unix text editor was Emacs, but I struggled with it. When I began running Minix on my Atari, it didn’t have the RAM for Emacs, so I ended up trying vi (well, Elvis). I soon switched to vi everywhere, because I could get by comfortably with what I could remember of vi’s 100 keystroke commands. I’m sure Emacs is great for people who can deal with its 1,900 key bindings, but I’m not one of those people. I avoid word processors too.
Just as code is not the goal of programming, and features are not the goal of implementing programming languages, so features should not be the goal of application software development. Solving a problem is the goal. The ideal piece of software has just enough features to solve the problem, and no more. Software should generally avoid trying to solve multiple problems, unless they are problems that usually occur together and don’t occur elsewhere. For example, it makes sense for a web browser to also be a bookmark manager; it doesn’t make sense for it to be a VPN client — that belongs with general networking setup. It’s reasonable for a word processor to also be an outliner, because outlining is part of the writing process; it’s less reasonable for a word processor to also have features for making graphs from tables of data.
People into hi-fi understand this. You pick a good amplifier, and if you want to play vinyl you connect it to a turntable. If you want to play CDs, you connect a CD player. Generally, the more high end the amplifier, the fewer controls it has. You can buy an amplifier, turntable and CD player in a single unit, but it probably won’t be very good. The company that makes the best amplifiers probably won’t be the one that makes the best turntables. You want pieces that solve one problem and work together. That was the key lesson from the success of early versions of Unix.
Every option your program has is a clue that maybe you have lost the plot. GNU ls has 58 options. Fifty eight! And the problem is just “list the files in a directory”! macOS has nearly as many. ls -bloat, ls -complex — both valid commands. And as applications become more laden with feature bloat, they also become harder to test, and more bugs creep in.
Look at some SaaS examples. What do people want Zoom for? Video chat. What did Zoom decide everyone wanted? A “workplace” with word processing, messaging, email, a calendar, a spreadsheet, presentation software, and the obligatory chatbot. I’ve never used any of those things, they’re just junk clutting up the UI. How did Airtable become successful? They built a database simple enough for ordinary people to use like a spreadsheet. What did they decide everyone wanted? A “platform” with portals, project planning, agents, customer relationship management, image processing, and of course a chatbot.
Unfortunately many people have learned to choose things based on how many features they have. Partly this is because more features seems to mean more value, and partly it’s speculation that one day those other features might be useful. Often the people choosing which product to buy are senior management and have no idea what the day-to-day ground level problems actually are, so they go for bloated “platform” solutions that claim to solve every problem.
Feature-based purchasing and feature-based product development are also how we ended up with Internet-connected fridges. Meanwhile, here’s the entire control panel from the garage freezer I bought:

The problem is to keep frozen food frozen. The solution is a box that makes things cold. It needs a control to adjust the coldness. To keep things simple, it defrosts itself automatically. There are no other controls, because they aren’t needed to solve the problem of keeping things frozen.
Simple solutions exist, but we have to want them, and avoid being seduced by features.
The two video game companies
There was a time when video game companies would build their own game engines, but if you play a modern video game the chances are the loading screen or credits will either mention Unreal Engine or Unity. There are a few other pro game engines like Source and CRYENGINE, but the industry seems to be moving to a duopoly. Putting graphics on a computer screen is now such a complex task that there’s a shortage of people with the necessary knowledge to build and maintain game engines, particularly those sophisticated enough for AAA games.
When Unity changed their licensing to try to squeeze more money out of game developers, people got angry. The company backed off, but it’s only a matter of time before they try the squeeze again, or Epic tries the same thing for Unreal Engine. In the mean time, Unity have rolled out updated terms of service that prohibit you from training LLMs on their content. They’re allowed to use your data to improve their service, of course.
It’s not just the software
My own experience of complexity as the enemy started with CPUs. BASIC on the TRS-80 wasn’t fast enough to do what I wanted to do, so I began trying to learn Z80 machine code. I didn’t have a great deal of luck, but a year or so later I got a different computer with a 6052 CPU, and I took to that immediately.
The difference? The Z80 has 158 different types of instruction, and 16 registers (plus flags, stack pointer and program counter). Meanwhile, the 6502 has only 56 instructions, and only 3 registers.
Back in 1965, Gordon Moore — later co-founder of Intel — had noted that the number of transistors that could be crammed onto an integrated circuit roughly doubled every two years. But just because you can doesn’t mean you should.
Intel’s original Pentium contained 3.1 million transistors, double the number in the 486. Perhaps unsurprisingly, a bug crept in, leading the chip to compute some floating point division incorrectly. Intel performed a recall. But to get better and better performance when running compiled code, CPUs continued to get more and more complicated. Features like speculative execution were introduced — when there’s a branch in the code, the CPU will either guess which one will run, or run both in parallel and throw away the one that turns out not to be needed. These sorts of tricks did speed up software, but they also made the behavior of the CPUs hard to reason about. Eventually people worked out how to exploit the behavior to break security, with attacks like Spectre and Meltdown.
For a while it did look as if things might get simpler. The ARM2 only had 30,000 transistors compared to the 80386’s 275,000, but was twice as fast. Unfortunately these days an Apple ARM CPU has about as many billions of transistors as an Intel CPU.
So what’s my point?
I’ve digressed a lot, so let me summarize.
When I run through a list of things that are terrible about computers, I notice a common factor:
Google controls the web. Why? Complexity.
Web pages are slow and clunky on mobile devices. Why? Complexity.
Software is full of security holes? Complexity again.
Applications are bloated and use up multiple gigabytes of RAM? Complexity.
Your office software is barely usable? Complexity from feature creep.
A couple of companies control video game development? Complexity.
People use the wrong tools? The right tools are too complex.
People use LLMs to write code? Languages are too complex, libraries and frameworks are too complex, APIs are too complex.
It seems to me that the computer industry has been utterly failing to address complexity for decades, and the signs are everywhere.
The difficulty of developing secure, efficient software is a symptom of the underlying sickness: complexity. Now we’re using LLMs to make it quick and easy to generate complex and voluminous code. We’re treating the symptom, and making the problem worse by doing so. (Every problem has a simple and obvious solution that will make things worse.)
At the same time, it seems to me that simplicity isn’t valued. A commit which adds a feature using 3,000 lines of code will impress management. A commit that removes an unnecessary feature by deleting 3,000 lines of code generally won’t. We know that “lines of code” is a terrible metric, but we have to measure something, and other things are hard to measure. We don’t really know how to measure complexity in general. We can measure cyclomatic complexity of a given piece of code, but that doesn’t tell us anything about the complexity of the user interface or the conceptual complexity of the design as a whole.
I don’t have a quick fix, because simplicity is hard. Any moderately capable programmer can build a complex piece of software to meet a business need. What’s much harder is to build a simple piece of software to meet that same need. For starters, it means spending a lot more time up front on design, and being willing to throw away work you later discover you don’t need.
Maybe you don’t have a choice any more. Maybe you’re at a company that has decreed that you’re not allowed to write code, you’re only allowed to examine and touch up generated code. If so then you have my sympathy, but please try to fight against complexity in your spare time. You really don’t need those Internet-connected light bulbs.