---
title: "Does Vibe Coding work?"
date: "2026-05-18"
author: "Stefan Loesch"
summary: "Vibe coding is amazing — until your project hits a wall. Not a bug, not a performance issue. Complexity itself becomes unmanageable."
description: "Why software projects slow down — fractal feature growth and quadratic complexity — and how vibe coding with LLMs makes it worse without deliberate architecture."
keywords: "vibe coding, vibe coding limitations, LLM coding, AI software development, code complexity, fractal complexity, quadratic complexity, Claude coding, software architecture, DRY principle, refactoring"
tags: AI, Software Engineering, Vibe Coding
url: "https://aigon.ai/blog/2026-05-14-does-vibe-coding-work/"
---

I've programmed quite a few projects over the last 30 years — part hobby, part professional — in a variety of languages. As a quant I used C and C++, later PHP, then Ruby, and finally I settled on Python (also a bit of JavaScript, but I don't like to be reminded of that). A pattern that emerged over the years was that the first day was absolutely amazing. 

> "Wow! That is easy! Look at how much progress I made in a single day. That'll be a breeze!"

The first week is still pretty good, albeit not quite as impressive. From the first month onwards you start asking yourself what you actually did achieve recently and why you are still not ready. It all seemed so easy at first. Those thoughts crystallized in a discussion with my friend [Thomas Barker](https://www.linkedin.com/in/thomasbarker/) who told me something along the lines _"Well, maybe this slowdown is just natural."_ He had made the same experiences. That got me thinking, and eventually I identified two key issues that led to this slowdown.

1. The **fractal structure** of software projects, and
2. the **quadratic complexity** when adding features.


## Issues in traditional software development

So let's take those issues in turn:

**Fractal structure of software projects**. Often new features simply add detail to existing features. You start with something relatively simple, then add detail, add more detail, and so on. Each new feature is smaller than the previous one, but there are more and more -- the overall structure becomes a _fractal_ in a way as illustrated in the Koch-inspired diagram below.

![Fractal complexity: Koch snowflake iterations showing how perimeter grows exponentially while shape stabilizes](koch_redline_v2.png)

We start with a very simple system (the triangle), and then successively add features (in red). Initially the shape changes quite dramatically, but looking from afar this stabilizes quite quickly: iterations 4 and 7 look very similar. However, the perimeter (proxy for the work done) grows exponentially -- but because you are operating at a finer and finer scale it doesn't feel like it. Of course this is somehow simplistic, e.g. sometimes genuinely macro features are added etc. However, the sub-features experience is real.



**Quadratic complexity.** The second reason things slow down: every new feature has to interact with existing features. For simplicity let us assume that every feature interacts with every other feature. So if you look at the diagram below: implementing a fourth feature is still relatively benign, as it only interacts with three existing features. The 8th feature interacts with 7, the 16th with 15, and the 32nd with 31. The overall number of interactions therefore grows as $n(n-1)/2$ which is quadratic. If we assume that the effort grows linearly with the number of interactions then the work required to implement $n$ features grows quadratically.

![Quadratic complexity: adding a new feature requires connecting to all existing features](quadratic_nodes_v2.png)

Now again, one may object that this is overly simplistic -- not every feature interacts with every other feature, and the feature effort is not necessarily dominated by the interaction with other features. Those objections are all valid, more so in some projects than in others. But the underlying point remains: a priori there is a quadratic component when adding features to a system, and in fact as we will see later, a lot of system design is about managing that quadratic component. 

## Enter Vibe Coding stage left

We just entered the nascent era of _"vibe-coding"_ aka _"LLM-driven coding"_ and of course all of the above still applies. LLMs' big advantage is that they are _much_ faster coders than even the fastest human coders, by an order of magnitude. Also they are extremely knowledgeable about code conventions, libraries, APIs, obscure failure modes, especially on languages and issues that have been heavily documented on the Internet. They may not know everything a domain expert knows but they are close -- and essentially on every domain at once. However, there are also drawbacks. As a friend of mine expressed it:

> [Claude] is like a really brilliant and knowledgeable programmer, but unfortunately with ADHD

What he was alluding to was that LLMs are often not particularly thorough in their analysis, and they have a tendency to jump to the first solution that works. What they do not necessarily do is explore alternatives, and making sure what they do is in line with the current codebase. If it works it works -- what else could matter? This is partially driven by limited context windows, but even recent models with 1m token windows still have this issue. Part of the problem is that they are often missing the forest for the trees -- they look at microstructure of the codebase, but they have a priori no view on the overall architecture. Even -- and this is important -- even if the structure is explicitly documented, they may still ignore it. It often is hit or miss.

The first practical issue that arises is that if the code base is too big and convoluted there comes a point where LLMs can simply no longer advance: whenever they change something at one place, they break something else. 

An earlier sign of deterioration is the code becomes very not-DRY ("Don't Repeat Yourself"). LLMs have a tendency of reinventing the wheel over and over. If unchecked, rather than adding a parameter to an existing function, or slightly restructuring the project, they will reimplement code that does 95% of what existing code already does -- just slightly differently.  So you end up with multiple pathways that all do more or less the same thing. This is how vibe-coded projects can reach insane line-counts very quickly. LLMs love adding new code. Left to their own devices, they rarely remove code, consolidate, or refactor.

![Code duplication: LLMs add new internal features instead of modifying existing ones](code_duplication_v4.png)

What happens often with LLMs is shown symbolically in the diagram above. We have initially two external features in the system (circles) and one internal feature connecting them (diamond). Then we add two more external features, and the right way of doing this would often be modifying the internal feature to accommodate all four features (Structure A). LLMs often add new internal features instead (Structure B) -- and because they are written from scratch they may look very different from the existing internal feature, despite having 95% overlap.

That's the bad news -- programming is complicated, and there is a natural limit to how far LLMs can go on their own. They are extremely good at simple projects -- but there comes a point (usually very quickly if they are left to their own devices) where they can no longer advance.

The good news is -- there are ways to mitigate this. I will discuss those in an upcoming series of posts, but to not entirely end on a cliffhanger, let me present the key mitigants that must be in place to manage this:

- **Unit and Integration Tests** — so the LLM doesn't break one thing when it changes another
- **Linting** - both standard and structural linting to enforce architectural guardrails
- **Architectural guidance** — clear guidance the LLM must follow; ideally included in the linting rules but otherwise at least explicitly documented
- **Hierarchical documentation** — written for the LLM to read, big picture first, progressively more detail
- **DRY refactoring** — don't repeat yourself; refactoring is cheap with LLMs, and the less DRY the codebase is, the faster the LLM adds even more non-DRY code, which compounds the problem
- **Clean object boundaries** — contains the quadratic complexity within small, well-defined modules

So -- does vibe coding work? Absolutely. LLMs are the most powerful programming tool I've encountered in 30 years of writing code. They are fast, knowledgeable, and tireless. For small projects or well-scoped tasks, they are nothing short of transformational.

But there is a ceiling to vibe coding, in two specific ways. Firstly, pure vibe coding can get you to a small app or dashboard in no time. Where you can get to in the first day (or week) is genuinely impressive. The same slow-down still happens though, and this is what many people who are new to (vibe) coding are in the process of learning right now -- many of them are probably looking back after a few weeks and they wonder where all the time went, how much they got done on day and in week one. The problem remains the same, but the boundary has been pushed out further. And to be clear -- this often matters because now people can program their own personal tools and dashboards that would previously have been either out of reach, or would have been much harder to build and maintain with eg Excel.

Secondly -- there is an actual ceiling to pure vibe coding. If you just let the LLM work on its own, it will eventually hit a wall where it can no longer advance because of the complexity of the codebase. At least at the moment LLMs still require a software _architect_ to manage the overall structure of the codebase, and to ensure the LLM does not go off the rails.

How to do that? This is still an area where we learn something new every week, but certainly the tools mentioned above will be an important part of the solution. Read on in [Part 2: Testing and Linting — Keeping LLMs on the Rails](/blog/2026-05-26-testing-linting-for-vibe-coding-llms/).
