Five years of building software and running a company. A multinational holding company with four divisions. An acquisition. A team of developers. A few failures I don't talk about enough.
Here's what I actually learned.
1. Shipping Beats Planning
I've never regretted shipping too early. I've frequently regretted planning too long.
The first version of HMD Payments had exactly three API endpoints. This website has been redesigned four times. None of those first versions were good - but all of them were shipped, and all of them taught me something that planning never could have.
The market doesn't care about your architecture diagrams. It cares about whether your software solves a problem. You can't know if it solves a problem until people use it.
2. Every System You Build Reflects What You Understood at the Time
The early projects at HMD Developments were single-process applications. That wasn't a bad decision - it was the right decision for what we knew and what the projects needed at that point. The distributed, multi-service architectures came later because the requirements changed, not because we should have "done it right" from the start.
I see junior developers agonize over architectural decisions for projects that don't have users yet. Don't. Build what works now. Rebuild when you understand the problem better. The second version will always be better than the first - that's how learning works.
3. Technical Debt Is a Feature
Not all technical debt is bad. Some of it is a strategic choice - shipping faster now in exchange for cleanup later. The key is knowing which debt to take on intentionally and which debt is accidental.
Good debt: "We'll use a simple JSON file instead of a database for now." You know the limitation, you accept it, and you have a plan for when to upgrade.
Bad debt: "Why is this function 300 lines long and nobody knows what it does?" This wasn't a choice. This was neglect.
I track technical debt explicitly - a DEBT.md file in each project that lists known shortcuts, why they were taken, and when they should be addressed. Making debt visible prevents it from becoming invisible.
4. The Best Code Is the Code You Don't Write
Over five years, I've become increasingly aggressive about removing code. Every line is a liability - it can break, it needs maintenance, it increases cognitive load.
Before adding a feature, I ask: "Can we solve this without code?" Sometimes the answer is a process change, a configuration update, or a conversation with the user about what they actually need versus what they asked for.
The projects I'm most proud of are the ones with the least code for their capability.
5. Users Don't Care About Your Stack
Nobody has ever chosen Chat Guard because it's written in TypeScript. Nobody picked HMD Payments because we use PostgreSQL. Nobody visits this website because it runs on Next.js 15.
Users care about: Does it work? Is it fast? Does it solve my problem? Can I trust it?
Technology choices matter for developer experience and operational capability. They do not matter for user adoption. Choose technologies that make your team productive and your system reliable. Stop optimizing for résumé-driven development.
6. Hiring Yourself Is the Hardest Decision
As HMD Developments grew, I had to decide what to do myself and what to delegate. The hardest part wasn't finding good people - it was letting go of control.
The first time I reviewed a pull request and thought "I would have done this differently" but the code was correct, efficient, and well-tested, I had to resist the urge to request changes. Different doesn't mean wrong. My way isn't the only way. It was humbling.
7. Burnout Is a Technical Problem
I burned out once. It wasn't dramatic - no collapse, no breakdown. Just a gradual loss of interest, followed by decreased output, followed by mistakes in production. I wrote about this less directly in my first post about starting out, but the truth is the journey hasn't been linear.
Burnout happens when you consistently spend more energy than you recover. The fix isn't motivational - it's structural:
- Set boundaries. No coding after 10 PM. No exceptions, even when the deploy is urgent.
- Automate the tedious parts. If a task drains you every time you do it, automate it or eliminate it.
- Take breaks before you need them. Scheduled rest is cheaper than unscheduled burnout.
8. Open Source Is a Dialogue
Publishing code isn't charity - it's communication. Every open source project I've released has made me a better developer. Code review from strangers is humbling. Issues from users reveal assumptions you didn't know you had. Pull requests show you approaches you never considered.
I covered the licensing side in Open Source Licensing: What I Learned the Hard Way, but the human side is equally important. Open source works when it's a conversation, not a donation.
9. Write More Than Code
The habit of writing - blog posts, documentation, design documents, even this reflection - has compounded more than almost any technical skill. Clear writing requires clear thinking. If you can't explain a system in plain language, you don't understand it well enough.
I started this blog to share what I've learned. It became a tool for learning itself. Writing about a problem forces you to confront the gaps in your understanding. It's the most effective debugging tool I know.
10. Keep Building
Six years from now, everything I know will be partially wrong. Frameworks will change. Conventions will evolve. The companies I'm running today will look different tomorrow.
That's fine. The point was never to be right forever. The point was to be useful now and to keep getting better.
Six years, one principle: build, learn, grow.