Why microservices aren't always the answer.

By Joseph Alexander

Microservices add operational overhead most teams aren't ready for. Learn when a well-structured monolith outperforms distributed systems — and when it's time to split.

The microservices hype cycle

Every startup founder has heard the pitch: break your monolith into microservices and watch your engineering velocity soar. Netflix did it. Amazon did it. So should you, right?

Not necessarily. Microservices solve real problems — but only if you actually have those problems. For most early-stage companies, a well-structured monolith is faster to build, easier to debug, and cheaper to run.

The real cost of going distributed

Microservices aren't free. Each service you extract brings operational overhead that compounds fast:

  • Observability: Distributed tracing, log aggregation, and metrics correlation across services require tools like Jaeger, Datadog, or Grafana — plus someone who knows how to use them.

  • Networking: Service discovery, load balancing, retry logic, circuit breakers, and timeout configuration. One misconfigured timeout can cascade across your entire system.

  • Deployment: Each service needs its own CI/CD pipeline, container registry, health checks, and rollback strategy. That's not one pipeline — it's dozens.

  • Data consistency: Distributed transactions are hard. Eventual consistency is harder to reason about. Saga patterns add complexity most teams underestimate.

When a monolith wins

A modular monolith — one codebase with clear internal boundaries — gives you most of the organizational benefits of microservices without the operational tax. You get:

  • Single deployment pipeline

  • Simple local development

  • Straightforward debugging with stack traces that don't span network boundaries

  • ACID transactions across your entire domain

If your team is under 20 engineers and you're not handling wildly different scaling requirements across features, a monolith is almost certainly the right call.

When to actually split

Microservices make sense when you have genuine forcing functions:

  • Team boundaries: Multiple teams stepping on each other's code, unable to deploy independently.

  • Independent scaling: One feature needs 50x the compute of everything else.

  • Technology diversity: A specific problem domain genuinely requires a different language or runtime.

  • Deployment independence: You need to ship one feature without risking the stability of unrelated features.

The pragmatic path

Start with a modular monolith. Enforce clear module boundaries. Use well-defined internal interfaces. When a real forcing function appears — not a hypothetical one — extract that specific module into a service. This is the strangler fig pattern, and it works because you're making decisions based on evidence, not architecture astronautics.

The best architecture is the one your team can actually operate. Don't build Netflix infrastructure for a product that hasn't found product-market fit.

Follow me to keep in touch

Where I share my creative journey, design experiments, and industry thoughts.