As engineers, we’re naturally drawn to complexity, we love to think, challenge ourselves and prove we are better every day. New frameworks appear, architectural patterns trend, and tools are constantly marketed as the next big thing. Curiosity pushes us to explore them, often before asking a simple question: Do we actually need this?
After 20+ years building software and running businesses, I keep seeing the same pattern: simplicity scales better than complexity. Not just in code, but across teams, costs, and product evolution. In this post I’ll just focus on the engineering side of code/architecture: technical debt, decision-making, and why pragmatism consistently outperforms novelty.
The misunderstood nature of technical debt
First things first: Not all technical debt is bad, in fact, technical debt is an indicator of thought prioritization and good execution.
There’s intentional debt: a conscious trade-off taken to move faster. Hard-coding a value in an MVP instead of building a flexible system is often the right call. It’s cheap, reversible, and helps validate assumptions quickly.
Then there’s the accidental debt: complexity introduced without clear purpose, over-engineered abstractions for the sake of it, fragile systems that look cool at first… This kind of debt adds up and slows down future work, increases bugs, and raises the cost of every change.
A senior engineer knows to time it well, when to take on debt and when to pay it down. Sometimes you may need to have business insight that depending on company culture it becomes opaque. That’s why I always try and explain as much about the business side as I can to my team, being transparent works wonders.
A practical approach:
- Delay non-essential work until there is a real need.
- Leave code better than you found it: as you touch code, improve whatever is around it.
- Refactor parts related to your task: avoid large, isolated rewrites.
Aim for simple, clear code that may need a refactor in five years.
The myth that complexity equals sophistication
Complexity is often thought as professional. I sometimes see MVPs and prototypes and alpha versions already running microservices, queues, and orchestration layers before they even have users or time enough to iterate a few versions and testing. These products look impressive but often become end up slowing everything down the road. Even when using AI to code and while it doesn’t take more than a few minutes, it ends up generating thousands of lines of code that will become a nightmare to maintain and to evolve or pivot at a later stage if the business ever needs to find or adjuts market fit.
There’s two main drivers for the complexity issue:
- Premature optimization; solving problems that don’t exist yet, just to be safe and avoid work in the future. Sadly, 80% of the time you’ll end up not making use of it.
- Career-driven choices; picking tools for CV value instead of product value. It sounds weird, but I’ve seen developers try and show off their skills unjustifiably, at the cost of maintenance and company speed. This is usually not done on purpose or maliciously, but it drains energy from the whole engineering team over time and digs a deep hole as time goes by.
Simplicity as a competitive advantage
“Everything should be made as simple as possible, but not simpler.”
—Albert Einstein
Simplicity is not about cutting corners but about making solutions obvious. The most productive code bases remove unnecessary layers instead of adding new ones. Features can always be added later, but unnecessary complexity tends to stick and is hard to get rid of. Even AI has a a hard time refactoring for simplicity.
- Fewer bugs: less code means fewer failure points.
- Faster onboarding: new engineers understand the system quickly.
- Lower costs: fewer dependencies and simpler infrastructure.
- Better adaptability: fewer moving parts make refactors easier.
Simple systems let the whole team focus on delivering value instead of maintaining complexity.
The scalability paradox
Designing for massive scale from day one feels responsible, but most of the time is a waste of energy and time, both at the time of coding and in the future while maintaining.
However the product/service/tool will be used rarely match whatever was assumed and scoped in the beginning, even the best product managers can’t predict accurately enough. Products evolve constantly, markets shift direction every week, and constraints change. Many systems built for global scale end up serving mostly local traffic.
Scaling too early means paying a complexity tax without revenue to support it.
You should build for current needs, maybe predict the next year on the “best case scenario plan”, but that’s it. When a real demand appears, scaling becomes easier to justify, both technically and financially. You will be able to justify the budget to hire more hands or get new infra.
The “shiny tool” trap
Curiosity is something we value on employees and company culture, but in production it comes at a cost and it is a mistake to accept it all the time.
Adopting new technologies introduces hidden overhead:
- Training; the entire team needs to learn it.
- Maintenance; evolving APIs and unexpected bugs.
- Hiring; smaller talent pools increase cost and time.
- Stability risks; new tools change quickly.
What looks like progress can quietly become long-term friction.
Industry-driven complexity
The industry often amplifies unnecessary complexity.
A static site becomes a full SPA with SSR, dozens of dependencies, and cloud orchestration. Sometimes justified, often not.
Large companies solve problems you don’t have. Their solutions are rarely your defaults.
Start with the simplest solution that meets your requirements. Reassess only when those requirements change.
Engineering with business awareness
The best engineers understand the business behind the code.
They know software matters only when it moves real metrics. This mindset leads to better trade-offs, faster delivery, and fewer unnecessary detours.
It also enables clearer communication: explaining costs, prioritizing impact, and aligning technical decisions with business outcomes.
Practical tips
- Start simple and grow when needed.
- Keep improving the codebase as you go.
- Use technical debt when necessary.
- Optimize based on data, not assumptions.
- Avoid trend-driven decisions.
- Prioritize readability over cleverness.
- Think in terms of engineering ROI.
Simple baselines to validate decisions
Before adding complexity, compare against a simpler option:
- Content delivery; static files + CDN.
- Background jobs; cron or managed queues.
- Data; a single relational database.
- Architecture; a modular monolith.
- Configuration; environment variables.
If the simple version clearly fails measurable requirements, then increase complexity. Otherwise, ship.
The cost of clever code
Clever solutions rarely age well.
As teams grow, non-obvious patterns become friction. Prefer clarity, predictable structure, and straightforward naming.
Establish lightweight standards: error handling, logging, and dependency guidelines. Consistency reduces cognitive load and improves speed.
Final thoughts
Software is always a trade-off, but one principle holds:
Simplicity scales. Complexity costs.
Build for today. Introduce complexity only when it’s justified by real constraints or real data. Complexity will come eventually; there’s no need to invite it early.

3 comments
Great article, thank you for all the tips. As a mid dev that wants to become senior there’s so much stuff i overthink… I should focus more on the long term
Simplicity is underrated in tech, give me the boring and predictable! I’ll break stuff up with fancy code in my own free time, thank you
simplicity is something you ignore until you realize the frankenstein project you are painfully maintaining is a fucking nightmare and not fun anymore. most of us learn it the hsard way