Early in my career, I built a lot of prototypes. MVPs with tight budgets, tighter deadlines, and one clear goal: work well enough to demo. There were no tests, little to no CI/CD, and no real discussion about architecture. The software only needed to support a handful of carefully prepared scenarios and integrate with a few APIs. If the demo succeeded, the customer would decide whether the product was worth building properly.
These systems were throwaway by design. Longevity was not a requirement, so architecture was not a concern.
Later, I moved into product development. We were building for real users, real usage, and real growth. Architecture suddenly mattered a lot more. But the underlying forces stayed the same.
Deadlines still controlled the decisions. Budgets still mattered. The main difference was that scale entered the picture. Not as an ambition, but as a concrete question: what does this system actually need to handle?
Scale Is a Constraint, Not a Goal
This is where architectural discussions often go wrong. Scale turns into a goal instead of a constraint. Future growth becomes a justification for present-day complexity.
As a part-time freelancer, I have worked with early-stage founders at the very beginning of their journey. Small teams, limited budgets, and often no users at all.
Yet it is common to see architectures designed for a future that does not yet exist. Kubernetes clusters, microservices, event-driven pipelines. Not because the product requires them today, but because it might need them someday.
Most of the time, it does not. Certainly not early on, and if you do, it's a good problem to have!
What Most Products Actually Need
What these products usually need is something that works. A monolith and a Postgres database can go very far. Vertical scaling is often sufficient for years. Adding complexity early rarely buys safety. It mostly buys cost.
Cost in infrastructure.
Cost in development speed.
Cost in cognitive load.
What We Really Mean by Requirements
When we talk about requirements or constraints, we are not just talking about features. Architectural decisions are shaped by multiple types of constraints:
- Business requirements like deadlines, budget, and time to market.
- Non-functional requirements like latency, throughput, availability, and consistency.
- Organizational constraints like team size, experience, and operational maturity.
- Product reality like current usage, realistic growth, real customers, and actual revenue.
If you cannot point to a concrete constraint in one of these categories, you are not solving a problem. You are guessing.
Premature Complexity in Practice
Consider a small product team of four engineers building a B2B SaaS product. At this stage, the product has fewer than fifty active users and only a handful of paying customers. Despite this, the team invests early in a microservices architecture.
The system consists of eight services, a message broker, distributed tracing, and a managed Kubernetes cluster. Every feature requires changes across multiple services. Local development is slow and brittle. Deployments are frequent but risky, rolling back is even worse. A significant portion of engineering time goes into keeping the system running rather than improving the product.
Infrastructure costs alone exceed a few thousand dollar per month. More importantly, development speed suffers. Simple product changes take weeks.
Eventually, the system is consolidated into a simpler architecture. Fewer services, fewer moving parts, and fewer failure modes. Deployment becomes boring and simple. Development speeds up. Nothing about the product’s requirements has changed. Only the architecture has.
Microservices are not the wrong choice in principle. But they can be the wrong choice for that moment.
Pragmatism as an Architectural Strategy
This is why pragmatism is an architectural choice.
Pragmatic architecture optimizes for:
- speed of change,
- low cognitive load,
- controlled costs,
- and the ability to learn from real usage.
It deprioritizes:
- theoretical scalability,
- architectural purity,
- and solutions to problems that have not yet materialized.
Let Architecture Earn Its Complexity
This does not mean ignoring the future. Successful products do outgrow their initial designs. Re-architecture is sometimes necessary. But those changes are justified by evidence. Usage patterns, performance bottlenecks, revenue growth, or operational pain.
They happen because the product has earned them.
Architecture should evolve with the product, not ahead of it.
Honesty Over Hypotheticals
Good architecture is less about preparing for every possible future and more about being honest about the present. Build systems that serve the product as it exists today and can grow based on evidence, not assumptions. Leave room to change when reality demands it.
That kind of pragmatism is not a compromise. It is a responsibility of every software engineer.

Top comments (1)
Check out my other posts on joachimz.me/!