I’ve been designing distributed systems since before they were cool.
At Microsoft, I worked on the predecessor to Azure. At that time, there were no System Design best practices. We were just a bunch of engineers trying to figure out how to make services talk to each other without crashing in prod.
Later, at Facebook, we embraced microservices. They gave us autonomy, fault isolation, and speed. But they also introduced a tax: debugging, deployment overhead, and complexity we couldn’t always see coming.
When we started Educative, I figured we’d do the same: build fast, split early, scale with services. But as we grew, I found myself questioning that assumption. Microservices gave us flexibility, but it also came with headaches.
Rather than unlocking true agility, microservices often slow down engineering. Debugging issues across distributed systems became time-consuming. Developers spent more energy maintaining service boundaries and managing API dependencies than building actual product features.
What began as an elegant solution for large-scale challenges started to look like over-engineered
I'm not here to bash microservices. They're still the right choice for some problems. But years of real-world implementation has revealed a lot about their tradeoffs.
As a result, a quiet evolution has been reshaping System Design.
Instead of microservices, engineers have begun embracing simplicity. They're building modular architectures that offer clarity and control, without returning to the pitfalls of traditional monoliths.
And in a twist of the times, AI is accelerating this shift, as AI prefers clean, consolidated systems over spaghetti service maps.
Let's look at the current state of System Design and what it means for the future.
I'll cover:
The hidden costs of microservices
Why simpler architectures are gaining ground
Emerging system architectures that restore order and speed at scale
How AI is tipping the scales toward consolidation
When to choose simplicity over fragmentation
What I wish we'd all known earlier about microservices
Let’s begin.
The 2010s were a turning point for software development. Cloud platforms like AWS and the rise of CI/CD transformed releases from quarterly events into daily routines. Speed soared, but so did complexity.
Legacy bottlenecks like server provisioning and approval chains gave way to new ones: tangled codebases, unclear ownership, and coordination overload. Microservices arrived as the answer. By splitting systems into independently deployable services owned by small teams, companies gained agility, tech flexibility, and fault isolation.
Suddenly, architecture could mirror team structure, and for a while, it worked brilliantly.
Before CI/CD, releases were rare and risky. CI/CD made them fast, but chaotic. Microservices emerged as the fix, enabling independent teams to ship quickly and safely in the cloud-native world.
This shift from monolithic to microservices allowed companies to organize their systems around distinct business domains. For instance, a team managing a search feature did not need to worry about how payments were handled. Each team owned its service and could build, test, and deploy independently. Below are some real-world examples that highlight the power of microservices architecture:
At Amazon, microservices allowed teams to deliver features without waiting for company-wide releases.
At Netflix, microservices offered the fault isolation needed to keep streaming uninterrupted, even if one part of the system failed.
At Uber, microservices made it possible to support diverse global features and payment systems.
For large-scale companies, microservices became a necessity. The approach enabled autonomy at the cost of complexity. But smaller companies found themselves struggling with unexpected hurdles after adopting microservices. We’ll now delve into those hidden costs, where the initial promise of flexibility gave way to a cumbersome reality.
Microservices initially delivered flexibility, speed, and autonomy, and for a time, they lived up to that promise. However, as the architecture matured, complexity grew, and what once felt like freedom soon became difficult to manage. Eventually, the bill came due, and it wasn’t cheap.
Teams at SpaceX and Tesla stress a key insight: “The most common error of a smart engineer is to optimize a thing that should not exist.” In microservices, this often means adding complexity without questioning necessity. Simplify first, eliminate what’s unnecessary, then optimize.
Let’s unpack where the cost really showed up, not in dollars, but in time, energy, and mental overhead.
Fragmentation and operational overhead: The initial freedom of splitting monoliths soon gave way to chaos. Teams managed countless fragmented repos with inconsistent tools and stacks, drastically increasing operational load. Every service needed its own pipeline and monitoring, dragging engineers into unfamiliar on-call rotations and creating alert fatigue. What looked like autonomy often became fragmentation disguised as freedom.
Performance regression: Each new service added latency, making fast in-process calls into slow and failure-prone API requests. Circuit breakers and retries offered some resilience, but complexity piled up. Minor service hiccups sometimes triggered major system-wide failures, revealing the hidden cost of inter-service communication.
Cognitive burden and data silos: Excessive decomposition overwhelmed engineers, who had to mentally map dozens of interdependent services. Debugging became a forensic task across disconnected logs and timelines. Meanwhile, siloed data and tightly scoped APIs led to constant “contract” negotiations between teams, shifting focus away from actual feature development.
The following illustration highlights some of the pros and cons of microservice architecture:
We expected speed, scalability, and autonomy. Instead, we got complexity, coordination overhead, and fragmented ownership. Microservices solved some problems but introduced a whole new set.
Microservices were not a mistake. They were a solution to real problems at scale. But the hidden tax they introduced changed how teams think about architecture today. As the industry evolves, companies are re-evaluating these trade-offs, which are leading to a new era of simplicity.
Following the 2020s, many companies appeared to face economic pressures and tighter tech budgets, which have contributed to slower hiring trends across the industry. As a result, companies had to rethink how to achieve more with leaner teams. The growing complexity of microservices was draining time and energy—debugging tangled distributed traces, onboarding new engineers took months, and deployments sparked anxiety over hidden dependencies.
Meanwhile, AI began transforming developer workflows, shifting focus from adding headcount to empowering smaller teams to work faster and smarter.
The real bottleneck was no longer the database, the users, or the compute—it was the platform’s growing complexity.
If simplicity is the future, does that mean AI agents will take over the complexities of microservices?
Are we heading toward a world where machines orchestrate everything, leaving developers to focus purely on strategies?
To explore more such questions, dive deeper into “Rethinking Microservices with the Rise of AI Agents.”
To be clear, this is not a return to yesterday’s monoliths. The modern push for simplicity is different. It is not about putting everything in one repo and hoping for the best. It is about making clear, intentional trade-offs to reduce the hidden overhead of complexity.
Modern simplicity involves the following:
Fewer, well-defined services: Create services with clear, focused responsibilities, each dedicated to a specific business function. This reduces sprawling complexity and makes it easier for teams to understand and manage. For example, the following illustration shows an e-commerce system having well-defined services:
Shared tooling and clear ownership: Companies are building platform teams and
Co-located business logic: Instead of splitting every tiny piece of code, we group related logic together. This makes features easier to build and debug, cuts down on constant inter-team negotiations, and minimizes performance hits from too much inter-service communication.
So, what does this new era of simplicity look like in practice? Let’s explore some of the emerging architectural patterns that are helping teams achieve simplicity in real-world systems.
As microservices reached their peak complexity, many teams began searching for a better balance, one that offered modularity without introducing overwhelming complexity or excessive division. This led to a practical shift toward designing systems that are simpler, yet still flexible for the long term, directly tackling the hidden costs we previously explored.
Let’s look at the key approaches that are now leading the way, demonstrating how leading companies are embracing a new era of architectural clarity. These core approaches include:
Modular monolithic architecture
Microservices, but a coarser architecture
Service blocks or sub-apps architecture
Let’s discuss them one by one:
Modular monolithic architecture is a System Design approach where a single application is structured into independent, well-defined modules that represent different business capabilities. All modules run in the same process, share a single deployment unit, and often the same database, but maintain strict boundaries through interfaces and encapsulation. It provides the modularity benefits of microservices without the complexity of distributed systems.
The following illustration shows a modular monolithic architecture of an e-commerce system:
For instance,
A microservice but coarser architecture refers to a System Design where services are still independently deployable and loosely coupled, like traditional microservices, but each service is larger in scope and handles a broader domain. Instead of splitting functionality into dozens or hundreds of fine-grained services (e.g., AuthService, EmailService, NotificationService), a coarser approach might group related concerns into a single, cohesive service (e.g., a UserOperationsService that handles authentication, user profiles, and notifications).
This approach balances modularity with operational simplicity, reducing inter-service communication, deployment overhead, and debugging complexity, which makes it a popular middle ground between monoliths and hyper-granular microservices.
Netflix, a microservices pioneer, has spoken about strategically consolidating services that don’t truly require independent scaling to optimize performance.
In a service blocks or sub-apps architecture, a large application is broken into smaller, modular units inside the same codebase and deployment. Each unit, or “sub-app,” is focused on a specific business area, like orders, billing, or user accounts. These sub-apps contain everything they need: their own routes, business logic, and sometimes even their own database layer. The key idea is that you get the benefits of separation (like microservices) without the overhead of managing multiple services, networks, or deployments.
For example, imagine you’re building an e-commerce platform. Instead of one huge, tangled codebase, you structure it into sub-apps like Products, Cart, Checkout, and Users. Each sub-app lives in its own folder, follows a standard structure, and can be worked on by a dedicated team. But everything still runs together as one deployable app. This way, the system stays modular and maintainable without requiring separate microservices, APIs, or DevOps complexity. It’s a sweet spot between a monolith and microservices.
In a service blocks or sub-apps architecture, where does each unit or “sub-app” typically live?
In a separate microservice repository
In its own folder within the same codebase
On a distinct server
Across multiple databases
These emerging patterns reflect a mature understanding of System Design trade-offs. The focus has shifted from breaking everything apart to intelligently grouping what belongs together, all in pursuit of greater velocity, reduced cognitive load, and sustainable system growth. This is the practical application of simplicity as a superpower.
Let’s compare the different simplicity patterns that are described above:
Aspect | Modular Monolithic Architecture | Microservices, but Coarser Architecture | Service Blocks/ Sub-Apps Architecture |
Definition | A single deployable unit with clear internal modules | Larger-grained services, fewer and broader in scope | Self-contained app clusters with shared runtime boundaries |
Deployment | One unit, one pipeline | Separate deployables, but fewer than traditional microservices | Typically deployed as a unit or in a coordinated rollout |
Data Management | Shared database with internal modular access control | Each service may have its own DB or a shared subset | Usually shared DB within the block |
Use Cases | When tight coordination and simplicity matter | When microservices are overkill, but separation is needed | When UX or flows need to feel seamless and bounded |
AI Friendly | Moderate, requires clear modular boundaries | Yes, simpler context for agents per service | Yes, agents can operate per block or sub-app |
We’re seeing a growing movement toward simpler software architectures, and most of these approaches, whether modular monoliths, coarser microservices, or service blocks, are part of a broader shift called consolidation.
So, what is consolidation?
Consolidation is the practice of merging many smaller, fragmented services into fewer, larger, and more cohesive units. Instead of dealing with a tangled web of fine-grained microservices, teams are regrouping functionality into coarser-grained modules, service blocks, or subapp architectures.
This approach embraces simplicity without sacrificing modularity, reducing operational complexity, clarifying ownership, and easing coordination across teams.
Beyond these architectural changes, another major force, artificial intelligence, is accelerating this trend toward consolidation and simplicity. Let’s see the role of AI in driving consolidation:
Just as cloud-native tooling once reshaped how we build systems, AI is now reshaping how we think about designing them. As large language models (LLMs),
AI systems work most effectively when they have a clear and unified view of the environment or problem they’re addressing. Whether it’s writing tests, generating code, or orchestrating workflows, LLMs and agentic systems perform best when they have a clear, consolidated view of the system.
In highly distributed microservice environments, important context is scattered across dozens of repos, services, and pipelines. This inherent distribution makes it harder for AI tools to reason holistically about a system’s behavior or potential issues. In contrast,
Generate code: By understanding the surrounding logic within a cohesive boundary, leading to more accurate and reliable outputs.
Write tests: By seeing both behavior and boundaries more clearly, ensure comprehensive test coverage.
Debug issues: By tracing problems end-to-end without constant reliance on opaque service lines and distributed logging, we significantly reduce cognitive load.
This fundamental preference for a unified context directly translates to higher developer productivity and more effective AI assistance, pushing architectures toward less fragmentation and greater consolidation.
Understanding these emerging patterns and AI’s role in favoring consolidation is one thing, but how do you actually make these choices in your own organization? Let’s explore a concrete framework for evaluating these trade-offs.
The pressure to fragment early often comes from mimicking the wrong scale, not meeting real needs. Every architecture, however, carries a cognitive cost. More moving parts mean more coordination, more failure modes, and more time spent managing glue code instead of building a product.
So, when is it truly worth it to embrace simplicity over fragmentation? Let’s see in the table below:
Decision Lens | Choose Simplicity | Choose Microservices |
Team Structure | Small team with shared ownership | Multiple independent teams moving in parallel |
System Scale | Moderate load with low fault impact | High scale with isolated failure domains |
Tech and Deployment Needs | Unified stack and shared release cycle | Polyglot services with independent deploys |
The table above offers a guide for making smart architectural choices. Remember, there’s no single “best” solution; the right path always depends on your team, product, and goals.
So, what have practitioners learned on the front lines of this architectural evolution? Let’s explore the hard-won wisdom of those who’ve been there.
What if the real innovation in System Design comes not from breaking systems apart, but from knowing what to keep together?
Some of the hardest-earned lessons didn’t come from blog posts or conference talks, but they came from late-night pages, broken pipelines, and painful migrations.
After years of building, scaling, splitting, and sometimes re-stitching systems, here are 5 lessons we just had to learn the hard way:
Don’t optimize for day-one scale. Most systems naturally grow complex; we should delay that complexity more wisely.
Services that communicate a lot belong together. We often decomposed too fast, prioritizing abstraction over true cohesion.
Architectural fixes don’t solve people’s problems. Misaligned incentives or unclear ownership leak into code but can’t be patched with it.
Pay attention to silent drains. Alerts, long deploy times, and coordination overhead are direct signs of hidden architectural debt.
Prioritize changeability over trendiness. The best systems are the easiest to change.
Amazon’s famous “two-pizza teams” worked because those teams owned cohesive systems—not scattered nano-services duct-taped together.
We’ve moved from microservices toward designs that prioritize flow, clarity, and resilience over ceremony.
Simplicity isn’t regression. It’s precision. It helps you think, ship, and recover faster without losing a grip on your system.
In System Design, every service you add expands the surface area, and every surface is a potential fracture.
So, take a hard look at your architecture:
Are you truly adding value, or are you subtracting clarity?
If you had to rebuild it all tomorrow, would you still split it the same way?
Our hands-on courses can help you learn from real-world case studies and master the patterns that actually work: