Blocks on blocks

What has playing Tetris got to do with software development? Quite a lot as it turns out, and not just as something to kill time while you’re waiting for your AI agents to complete the plans for your next Death Star. For a while now I’ve been struggling to find a good metaphor to explain sustainable development to a non-technical audience. It’s one of those subjects that doesn’t lend itself to abstractions.

Even discussing it within the developer community is tough. Newer developers have rarely worked on a system for long enough to experience what happens as the code base grows in size and complexity. Many senior developers have experienced this, but take it as a law of nature that change just gets harder as time progresses. Eventually you just have to throw it away and start again.

Enter the agents

Now there is a third community entering the fray. The agent wranglers are making great strides in building teams of agents that can take much of the programming load. Some rely on close human scrutiny and direction; others step back completely from the code and focus on the tests and specifications. This last group are convinced that there is no value in checking the code. They often go further and suggest you can throw it all away at any stage and recreate it based on amended specs.

If you buy in to these claims, you may well conclude that code structure no longer matters. Clean code was something that mattered when people were involved in programming. It no longer matters when LLMs are doing the work.

This might turn out to be true, but it might also be wrong and that’s a big deal. Trusting this position is a big leap to take when the future ability to change your system depends on it. The reality is that at the time of writing this article (July 2026), no-one has been building and running a system using this approach for long enough for the consequences to be understood.

First do no harm

In medical terms, we’re in the very early phases of a clinical trial. The treatment shows some promise, but results are varied and long term effects are an unknown quantity. If you plough ahead with widespread use, by the time you do know, it’ll be too late.

What is clear is that if you succeed, you’re ahead of the competition. Way ahead. If you fail, however, you fail big and there’s no way back. It’s too late to introduce humans to the code, especially if it hasn’t been written for humans to read. What you have is a dead system, because a system that can’t change is no use in an ever changing market.

So, how does Tetris help in this conversation? I’ve been trying to find a way to share how it “feels” to maintain a clean system and how that makes change easier, rather than harder over time. This morning, in the shower, my brain worm of the day was Korobeiniki. (No, I didn’t know it was called that either – I had to google it. Many of us heathens know it as the Tetris theme tune, made famous by Nintendo. It’s actually a traditional 19th-century Russian folk song based on Nikolay Nekrasov’s 1861 poem.)

It’s all a game

If you haven’t played Tetris, welcome to planet earth. Let me take you to our leader. Basically, it’s an incredibly simple game where you place falling shapes made of equal sized square bricks onto the top of a growing wall. Every time you complete a horizontal line of bricks, the row disappears and the wall shrinks. There is added jeopardy too. The higher the wall gets, the less time there is to position the block. Pretty soon there isn’t time to think, and you’re doomed. If the wall reaches the top of the screen you lose. The longer you survive the higher the score. There is no way to win. The glory is all in the score.

Tetris teaches us two things about software product development. The first, and possibly the most important, thing it teaches is that sometimes one killer feature is enough. All you can do in Tetris is orientate the block as it falls, and yet it’s one of the most successful games of all time. Possibly the highest selling game ever, depending on how you measure sales.

If you were under the impression that adding features to products was the way to a great product then you already learned something invaluable from Tetris and you can stop reading here (assuming you didn’t already). If, however, you still want to know what this teaches us about software development in general, please read on.

Another brick in the wall

When developing a piece of software, it seems reasonable to assume that for each new feature, you need new code. You might therefore conclude that the more things a system can do, the larger it gets. The assumption is true, but the conclusion isn’t. Not if you want your system to continue to change over time without becoming unmanageable.

Imagine that code base is the Tetris wall and each new piece of code is one of those blocks. If rows never disappear, it’s a pretty short and frustrating game. Similarly, if all you do is add new code without touching the existing system, it too will be frustrating and short lived. Once the code grows beyond a certain size, the time taken to make each new change grows and the number of ways in which the system can fail also grows. Furthermore, you have to understand all of that code in order to change any of the existing features and there comes a point where that just isn’t possible any more. The wall reaches the top of the screen and the game ends.

Refactor or die

That’s where continuous refactoring comes in (and I’ve stressed the word continuous for a reason here). Imagine each new feature is a Tetris block and each square brick within a block is a specific function within that feature. Now imagine that each row in the wall contains bricks that all do very similar, but not quite identical things. At first, it’s not safe to just assume they’re all the same, but as more bricks are added to the row a clear pattern emerges. Eventually the pattern becomes clear and you can refactor all those separate pieces of code into a single reusable module, function or sub-feature.

The row disappears and the wall shrinks.

Fewer rows means less code; it also means simpler code. With less code you get fewer bugs (on average) and with simpler code you get safer modification. Reuse of the same code, instead of duplication, means bugs will surface more quickly because more features hit the same bug. It also has the added benefit that one fix will solve many similar issues. Without reuse, you might have to apply the same fix multiple times before the issue disappears completely. Chances are you’ll miss some.

That’s why refactoring as you go is so important in keeping a system healthy and maintainable over a long period of time. It has to be done continuously as each feature is added, otherwise the wall grows too high and you don’t have time to think. The less time you have to think (or the harder the thinking is) the more mistakes you’re likely to make. The more wall there is, the more ways there are to break it.

If, however, you keep condensing and pruning the code, you reach a point where adding a feature can reduce the overall size of the system rather than increase it. You actually get to add things by taking stuff away. So, how do you know you’re doing it right?

Vital signs

There are two simple measures you can use to judge the health of a system:

The first measure is code size. Initially, the code should grow at a constant rate, but as development progresses the average growth rate per feature added should decrease and eventually level out. If growth stays linear you have an issue. Eventually you’re going to exceed the manageable size and things start to grind to a halt.

This is where the second measure comes in. Initially each new feature has no existing code to benefit from. This means average time per feature is fairly constant. As you refactor and reuse, the amount of new code needed to implement a feature should decrease. It never falls to zero – you can’t get something from nothing – but it should become significantly quicker. If this measure stays constant, or worse still, increases, you also have an issue. Carry on that way for long enough and it will definitely increase until the cost to make a change exceeds any value it can deliver.

Obviously, these aren’t the only two measures you need to know you’re doing the right thing. Measuring value delivered is even more crucial, but these two measures are all you need to know you’re not heading for a brick wall and game over.

Does it still matter?

As I said at the beginning, you might think none of this matters anymore now your agents are doing all the coding. How do principles that applied to humans have any relevance in an LLM enabled world? You may be right. We might have finally moved beyond the “natural” laws that have governed software development for decades, and engineering endeavours for centuries.

The only way to be sure is to measure and find out. Plot the two graphs above as your system grows and see what happens. If things stay stable all is good, but if the graphs start to look like the red ones you’re heading for trouble. The question is, is it too late by then to do anything about it?

So that’s it. That’s why I think Tetris is a good metaphor for software development.

Next: “Pacman as an analogy for life and why eating people is bad”.