Every few months, a startup posts about how they built their entire platform on 47 microservices from day one. The comments are full of admiration. The reality is usually that they spent three months on infrastructure that a monolith would have handled in three days.

Microservices are a solution to a problem most early-stage companies do not have yet.

The Problem With Microservices Early

Microservices make sense when you have multiple teams that need to deploy independently without coordinating with each other. The architecture exists to solve an organizational problem - not a technical one. And when teams do adopt microservices too early, they almost always draw the boundaries in the wrong place.

When you have 5 engineers:

  • There is no coordination overhead between teams. You have one team.
  • Network calls between services add latency. Function calls in a monolith do not.
  • Distributed tracing, service meshes, and API contracts are overhead with no return.
  • Every cross-service operation that needs a transaction becomes a distributed transaction.

Distributed systems are hard. You get partial failures, network timeouts, and consistency problems that do not exist in a single process. You take on all of that complexity - and the hidden costs that come with it - before you have found product-market fit.

The Monoloith Gets Called Slow. It Isn’t.

The argument against monoliths is usually “you can’t scale them.” This is 2005 thinking.

Concern Reality
Can’t scale horizontally Stateless monoliths scale horizontally fine
Single point of failure Multiple replicas behind a load balancer
All-or-nothing deploys Blue-green and canary deployments work
Hard to change Depends entirely on internal structure

Basecamp runs on a Rails monolith serving millions of users. Stack Overflow served billions of requests per month from a handful of servers. Shopify was a monolith until it had hundreds of engineers. The constraint was never the architecture.

The Real Benefit: Speed

A well-structured monolith moves faster in the early stages.

In a monolith:

  • Refactoring crosses domain boundaries without API changes
  • Transactions are trivial
  • Local development is one docker compose up
  • Debugging is a single process
  • Deploys are one artifact

In a microservices setup:

  • Refactoring between services requires coordinating API changes
  • Transactions across services require sagas or distributed locks
  • Local development requires running 8 services
  • Debugging requires correlating logs across services
  • Deploys require coordinating service versions

When you are iterating on product weekly, the monolith wins every time.

The Modular Monolith Is the Right Default

A big ball of mud monolith is bad. A modular monolith is excellent. The distinction matters.

A modular monolith has clear internal boundaries between domains. Each module has a defined public interface. Cross-module communication goes through those interfaces, not direct database access or internal coupling.

/app
  /billing
    - models/
    - services/
    - api/
  /users
    - models/
    - services/
    - api/
  /content
    - models/
    - services/
    - api/

When billing needs user data, it calls a public function in the users module. It does not query the users table directly. This discipline pays off later - when you do need to extract a service, the module boundary is already defined.

When to Actually Split

Extract a service when:

  1. A team owns an independent domain - Billing has its own team, deploys on its own schedule, and has no reason to coordinate with content deploys
  2. A component has wildly different scaling needs - Your image processing pipeline needs 20 workers, your API needs 3
  3. You need technology isolation - The ML inference service needs a GPU runtime that conflicts with your web framework dependencies
  4. A clear interface already exists - The module boundary in your monolith is stable and well-defined

Do not extract a service just because a piece of functionality feels conceptually separate. Wait until the organizational or technical pressure is real.

A Concrete Timeline

A realistic path for a growing startup:

  • 0-10 engineers: Modular monolith, shared Postgres, everything in one repo
  • 10-50 engineers: Extract one or two services when teams form around domains
  • 50+ engineers: Microservices make organizational sense; invest in platform engineering

Shopify, Github, Etsy, Twitter - all started as monoliths. All split into services over time. The split happened when the team size demanded it, not because microservices are inherently better.

Bottom Line

Microservices are a pattern for scaling organizations, not for scaling code. Start with a modular monolith, enforce clean internal boundaries, and extract services only when you have a team and a reason. The discipline of building a good monolith makes you better at distributed systems when you eventually need them - because you understand the problem you’re solving, not just the pattern you’re following.