Lessons from 8 Years of Building Software

I started with WordPress: custom themes, plugins, e-commerce sites. Then I moved into full-stack work with React and Node.js, then into project management, and now I lead a team at my own company.

Looking back at that path, there are a few things I wish I had understood sooner. Not technical lessons, but the things that decide whether a project succeeds or fails.

The most expensive bugs live in the requirements phase

This is the lesson I paid the most to learn.

I have shipped projects where the code was clean, the architecture was tidy, the tests were there, and after handover the client said: this is not what I needed.

No amount of engineering saves you from that. The cost of fixing a misunderstanding during requirements gathering is close to zero. The cost of fixing the same misunderstanding after the code is written is tens of times higher, and after it is in production, higher still.

Since moving into project management, the thing I spend the most time on is no longer code, it is removing ambiguity before anyone starts typing. The question I ask most often in meetings is “what should the system do when this happens?”, and it usually exposes gaps that both sides assumed the other had already thought about.

Business people do not know what they want until they see it

That sounds like a complaint, but it is actually a very ordinary fact about people.

The client describes their booking process in words, you write it into a document, both sides sign off. Then they see the real screen and say “oh no, when a guest books multiple rooms it works differently”. That information was never hidden. It simply had not been thought about until there was something concrete to look at.

The practical conclusion: do not wait until handover to show the client something. A mockup, a wireframe, or an ugly working demo is far cheaper than rewriting code. On the hotel projects I have worked on, demoing early and often cut out most of the big last-minute changes on its own.

Estimates are always wrong, so design a process that accepts it

I have never met anyone who estimates accurately in a consistent way, myself included.

The problem is not estimation skill, it is the nature of the work: you are estimating time for something you have not done before. If you had done it before, you would already have code to reuse.

The way I handle it now is not to try to estimate more accurately, but to break things down further. An item estimated at three days can be off by a few days. An item estimated at three weeks can be off by a few weeks, and you only find out when it is far too late. Smaller pieces make the drift show up early, while there is still time to adjust.

Code is communication

You read code far more often than you write it. Optimise for the next person reading it, which is usually you six months later with all the context gone.

This gets even more true when you move into a management role. The code I write now is maintained by other people. A clever function only I understand is a debt I leave behind for the team.

Ship small, ship often

Large changes are risky and hard to review. A 2000 line pull request gets rubber stamped, because nobody can hold that much context in their head.

Small, frequent releases lower risk and keep momentum. And more importantly: when something breaks, you know exactly which change caused it.

Learn new tech, but pick something solid to stand on

In 8 years I have gone through jQuery, WordPress, plain PHP, React, Node.js, and now AI coding tools. The tools change constantly.

But the things that carry across from one era to the next are few and they barely change: understanding how HTTP works, being able to design a database, reading logs to trace a root cause, and explaining a technical problem to a non-technical person.

That last skill is the most underrated one. It decides whether you stay in an execution role or go further.

Working with AI does not mean owning less of the result

On a recent project I built the frontend with AI assistance to deliver faster. It genuinely cut a lot of time.

But there is a line I keep very clear: the architecture and the final code are still my responsibility. AI producing code that runs is not the same as code that is correct, and it is definitely not the same as code that fits the rest of the system. If I do not understand a piece of code in my own project, it should not be there.

Pick the simplest solution that solves the real problem

The biggest temptation for engineers is building the interesting thing instead of the necessary thing.

I have designed systems that accounted for scale that never arrived. The extra complexity does not disappear. It stays and slows down every change that comes after.

The question I now ask before any architecture decision is: if I drop this part, what is the worst that can happen? A surprising number of times, the answer is “nothing”.

The one that matters most

If I had to compress all of the above into one line: understand the problem first.

The most expensive mistakes I have seen were not technical failures. They were doing the wrong thing very well. Slowing down at the start to move faster later is the highest return investment in this job.