Chapter 10 Interview: Microservices

🎙️ Practice answering these out loud — aim for 2–3 minute responses.

1. When would you break a monolith into microservices?

Honestly, I'd resist splitting until the pain is obvious. A monolith isn't inherently bad — it's simple to deploy, debug, and reason about. You split when the cost of staying together exceeds the cost of going distributed.

Concrete signals: deployment frequency is bottlenecked because ten teams touch the same codebase and step on each other. Or one module needs to scale independently — like your image processing eating all the CPU while your auth service sits idle. Or different parts have wildly different reliability requirements.

I'd also look at team structure — Conway's Law is real. If you have autonomous teams that own distinct business domains, microservices let them deploy independently. But if you've got 5 people? Keep the monolith. The coordination overhead of distributed systems will crush a small team.

The worst reason to split is "because Netflix does it." They have thousands of engineers. Most companies should start monolithic, build clear module boundaries internally, and extract services only when there's a forcing function.

  • Deploy bottleneck: multiple teams blocked by shared release cycle
  • Independent scaling: one component's resource needs differ drastically
  • Team autonomy: Conway's Law — architecture mirrors org structure
  • Don't split prematurely: modular monolith is often the right middle ground

2. How do you decide service boundaries?

This is where Domain-Driven Design really earns its keep. You want boundaries around business capabilities, not technical layers. Don't make a "database service" or "validation service" — that's just a distributed monolith with network hops.

I look for bounded contexts: areas of the business where a term means one specific thing. "Order" in the checkout context is different from "order" in the fulfillment context. If two things need to change together for the same business reason, they belong in the same service. If they change for different reasons, separate them.

Data ownership is the litmus test. Each service should own its data completely — no shared databases. If you can't give a service full authority over its data, you've probably drawn the boundary wrong. Cross-service queries are a code smell that says your boundaries need rethinking.

Practically, I'd start by mapping the domain with event storming — get business people and engineers in a room, map the events that flow through the system, and natural clusters emerge. Those clusters are your services.

  • Bounded contexts: where terms and rules are internally consistent
  • Change together → stay together: cohesion over technical separation
  • Data ownership: each service fully owns its data, no shared DBs
  • Event storming: collaborative discovery of natural service boundaries

3. What are the challenges of inter-service communication?

Oh, this is where microservices get real. The network is unreliable, and now every function call that used to be in-process is a network request that can fail, be slow, or return garbage.

First challenge: latency. A local function call is nanoseconds. A network call is milliseconds at best. Chain five services together synchronously and you've got compounding latency. Users notice. So you need to think async-first — events and messages where possible, synchronous calls only when you truly need an immediate response.

Second: partial failure. Service A calls B and C. B succeeds, C times out. Now what? You need retry logic with exponential backoff, circuit breakers so a failing downstream doesn't cascade, and compensation logic (sagas) to undo partial work. This is genuinely hard to get right.

Third: data consistency. No more ACID transactions spanning the whole operation. You're in eventual consistency territory. You have to design for the window where data is stale across services — and make the business understand that too.

Fourth: observability. When a request touches 8 services, where did it slow down? You need distributed tracing (correlation IDs through every hop) or you'll be debugging blind.

  • Latency compounding: synchronous chains multiply response time
  • Partial failure: retries, circuit breakers, saga compensation
  • Eventual consistency: no cross-service ACID transactions
  • Observability: distributed tracing is mandatory, not optional

4. How would you migrate from a monolith without downtime?

The Strangler Fig pattern — named after those tropical trees that grow around an existing tree and eventually replace it. You don't rewrite from scratch. You incrementally route traffic to new services while the monolith still runs.

Step one: put a facade or API gateway in front of the monolith. All traffic goes through this. Now you have a place to intercept and redirect. Step two: pick one well-bounded piece — maybe notifications or search — extract it to a new service. The gateway routes just those requests to the new service while everything else still hits the monolith.

The critical bit is running both in parallel during the transition. Dark launching — the new service processes requests but you compare its output to the monolith's without serving it to users yet. When you're confident the outputs match, flip the switch. If something goes wrong, the gateway routes back to the monolith instantly.

Data migration is the hardest part. You often need a period of dual-writes or change data capture streaming from the old DB to the new service's store. Eventually you cut over reads, then writes, then decommission the old path.

  • Strangler Fig: incrementally route traffic from monolith to new services
  • Facade/gateway: intercept layer that controls routing decisions
  • Parallel running: compare outputs before cutting over
  • Data migration: dual-writes or CDC during transition period

5. What's a service mesh and when do you need one?

A service mesh is infrastructure that handles all the cross-cutting concerns of service-to-service communication — retries, timeouts, circuit breaking, mutual TLS, observability — without your application code knowing about it. Think of it as a networking layer that lives alongside your services as sidecar proxies.

Each service gets a proxy (like Envoy) deployed next to it. Your app talks to localhost, the proxy intercepts and handles the messy network stuff: encrypting traffic, collecting metrics, enforcing policies, managing retries. Istio and Linkerd are the big implementations.

When do you need one? Honestly, not until you have enough services that the operational complexity justifies it. If you have 5 services, a mesh is overkill — just use a good HTTP client library with built-in retries. At 30+ services with multiple teams, the mesh pays for itself because you get consistent behavior without every team implementing their own retry/TLS/observability logic.

The downside is real though — it's another complex system to operate. Adds latency (small, but measurable). Debugging gets harder because there's an invisible proxy in every call path. It's a tool for organizations that have outgrown simpler approaches, not a starting point.

  • What: sidecar proxies handling retries, mTLS, observability transparently
  • How: proxy (Envoy) per service, controlled by a control plane (Istio/Linkerd)
  • When: 30+ services, multiple teams, need consistent cross-cutting policies
  • Trade-off: operational complexity and latency overhead vs. consistency