Microservices — Common Interview Questions and Answers
1. When should you actually use microservices?
When the costs (network, ops, data consistency, deploy complexity) are paid back by the benefits (independent deploys, independent scaling, team autonomy, polyglot, fault isolation).
Good fits:
- Multiple teams that step on each other in a shared codebase.
- Components with very different scaling profiles (a video transcoder vs an API).
- Different stacks make sense per component.
Bad fits:
- Small team / early product — the operational overhead exceeds any benefit.
- Tightly coupled domain where transactions span everything.
- “Microservices because it’s modern” — distributed monolith awaits.
A monolith done well (modular, well-tested, with good deploy automation) beats a poorly drawn microservice fleet for most teams.
2. Monolith vs microservices — concrete trade-offs?
| Monolith | Microservices | |
|---|---|---|
| Deploy | one unit | many independent |
| Local dev | single repo, single run | docker-compose hell |
| Tests | in-process, fast | needs contract tests, test envs |
| Transactions | ACID across modules | sagas, outbox, eventual |
| Scaling | scale whole app | scale per service |
| Polyglot | one language | per service |
| Team autonomy | code-level coupling | service boundary = team boundary |
| Failure mode | all or nothing | partial degradation possible |
Modular monolith (a monolith with strict module boundaries) is the middle ground a lot of teams land at.
3. How do services find each other?
Service discovery. Two patterns:
- Client-side — client queries a registry (Consul, Eureka, etcd), picks an instance, calls directly.
- Server-side — client calls a load balancer / sidecar; it resolves and forwards (Kubernetes Service + CoreDNS, AWS ALB, Envoy sidecar).
On Kubernetes you usually get it for free: http://orders.default.svc.cluster.local resolves via CoreDNS to the Service ClusterIP, kube-proxy load-balances across ready pods. Readiness probes gate inclusion.
4. Sync vs async inter-service communication — when each?
- Sync (HTTP/gRPC) when the caller needs the answer in the same request (UI fetches, search, lookups).
- Async (queue/event) for side effects, fan-out to multiple consumers, work that can retry without involving the caller, and decoupling components with different scaling/availability profiles.
Anti-pattern: 4+ deep synchronous chain. Latency multiplies, every link is a failure point. Refactor to fan-out via events.
See 02_inter_service_communication.md.
5. How do you do a transaction that spans two services?
You don’t — there’s no ACID across services in practice. Options:
- Move the boundary so the work is local to one service. Best fix when possible.
- Saga — sequence of local transactions with compensating actions on failure. Orchestrated (Temporal, Step Functions) for complex / long-running; choreographed (events) for simple flows.
- Eventual consistency — accept that downstream state catches up; design the UX around it.
2-phase commit (XA) across HTTP is theoretical, almost no one runs it at scale.
6. What’s the dual-write problem and how do you fix it?
“Atomically write to DB and publish event” is impossible without coordination — a crash between the two leaves one done and the other not.
Fix: transactional outbox. Write the event into an outbox table in the same DB transaction as the business write. A separate relay process polls the outbox and publishes to Kafka/SQS/etc., then marks rows as published. Events are at-least-once; consumers must be idempotent.
Alternative: Change Data Capture (Debezium) reads the DB write-ahead log and emits events. No outbox table, more infra.
See 04_data_consistency_patterns.md.
7. At-least-once vs exactly-once delivery?
Exactly-once delivery isn’t achievable over a lossy network — the standard trick is at-least-once delivery + idempotent consumers, which gives exactly-once processing (the effective semantic that actually matters).
Idempotency mechanisms:
- Idempotency-Key header on POSTs (server stores key → response).
- Dedup by
(source, message_id)on consumers, inserted in the same transaction as the side effect. - Natural idempotency via business identity (upsert by order_id).
8. How do you propagate trace context across services?
W3C traceparent header for HTTP. The OpenTelemetry SDK injects it on outbound calls and extracts it on inbound. The receiving service starts a new span with the inherited parent ID.
For Celery / message queues — the OTel Celery instrumentation serializes the context into the task’s message headers and restores it in the worker. Without that, traces dead-end at task.delay().
For async Python — OTel relies on contextvars, which propagate across await but not across raw threading.Thread or naive run_in_executor. Use contextvars.copy_context().run(fn) or the OTel-aware instrumentations.
See 03_distributed_tracing.md.
9. What’s a service mesh and do you need one?
Sidecar proxies (Envoy / linkerd-proxy) + a control plane (Istio / Linkerd) handling mTLS, retries, timeouts, traffic policy, and telemetry transparently to the app code.
Worth it when:
- Polyglot stack (you’d otherwise reimplement the same logic in 5 languages).
- mTLS between services is a hard requirement (compliance).
- Many services (~50+) needing uniform policy.
Skip it when:
- < 10 services in one language — libraries (httpx, tenacity, structlog) cover it.
- The control plane operational cost would dominate your platform team.
See 05_service_mesh.md.
10. Gateway vs mesh?
- Gateway — north-south (client ↔ cluster). End-user auth, rate limits, public TLS, request shaping.
- Mesh — east-west (service ↔ service). mTLS, retries between services, observability.
Both at once is normal: client hits API Gateway → ALB → pod with Envoy sidecar → another pod with Envoy sidecar.
See 06_api_gateway.md.
11. How do you scale microservices?
Per-service autoscaling on the metric that matches the workload:
- Stateless HTTP APIs — CPU or request rate (HPA on K8s, ECS service autoscaling).
- Queue consumers — queue depth (SQS approximate-receivers count, Kafka consumer lag).
- WebSocket servers — active connection count, often with sticky sessions.
Database is the usual scaling ceiling; don’t horizontally scale stateless services into a DB that can’t keep up. Pool sizes, pgbouncer, read replicas, sharding come first.
12. How do you test microservices?
Test pyramid stretched out:
- Unit — per service, pure.
- Integration — per service against real DB, mocked external services (or testcontainers).
- Contract tests — Pact or similar: producer and consumer agree on the message/HTTP shape; CI verifies both sides.
- E2E — sparingly. Slow, flaky, expensive. Reserve for the top user flows.
The contract test is the trick that lets you avoid full E2E for most changes.
13. How do you handle authentication across services?
End-user identity is established at the gateway by validating a token (JWT from Cognito / Auth0 / etc.). The gateway strips the raw token and injects an inward header (X-User-Id, X-User-Roles). Backend services trust those headers because they only accept gateway traffic.
Service-to-service identity is established by mTLS (service mesh) or service accounts (K8s + IRSA on AWS). The mesh’s AuthorizationPolicy enforces “service orders can call service payments on path X”.
14. What’s a saga? Orchestration vs choreography?
A saga replaces a distributed transaction with a sequence of local transactions, each with a compensating action that runs if a later step fails.
- Choreography — each service listens for events and reacts. No central coordinator. Decoupled but flow is implicit (hard to see end-to-end).
- Orchestration — central orchestrator (Temporal, Step Functions, Camunda) explicitly calls each step. Visible flow, easier branching, but the orchestrator becomes a key service.
Choose orchestration when flow is complex, branching, or long-running with human steps. Choose choreography for short flows where services are autonomous.
15. What’s the dead-letter pattern?
A queue/topic where messages go after exhausting retries. Why:
- Prevents poison messages from blocking the queue (poison-pill scenario).
- Surfaces failures for human investigation.
- Lets you re-process after fixing the bug.
Most brokers support it natively: SQS (maxReceiveCount → DLQ), RabbitMQ (DLX exchange), Kafka (the consumer writes failed messages to a *.dlt topic). Alerting on DLQ depth growth is a standard SLO.
16. Deployment strategies?
| Strategy | How | When |
|---|---|---|
| Rolling update | replace N pods at a time | default; downtime-free for stateless |
| Blue-green | two full envs; switch traffic | big-bang releases, easy rollback |
| Canary | route 1% → 10% → 50% → 100% | risk reduction with metrics-driven rollout |
| Shadow | mirror prod traffic to new version, ignore response | validate behavior under real load before serving |
| Feature flag | code already deployed, off behind a flag | decouple deploy from release |
In K8s: Deployment (rolling), Argo Rollouts or Flagger for canary/blue-green. AWS CodeDeploy supports canary natively for Lambda.
17. How do you handle a slow downstream service?
- Timeout on every call (mandatory). Defaults are too generous.
- Circuit breaker — after N failures, fast-fail without calling the dependency. Recovers after a probe succeeds.
- Bulkhead — limit concurrent calls to that dependency so it can’t starve the rest of your service.
- Async + retry queue if the work can wait.
- Graceful degradation — return partial data, cached data, or “feature unavailable” rather than a 500.
If you can pre-fetch or cache, do that. If the caller is a UI, make it async with a follow-up notification.
18. How do you do graceful shutdown?
On SIGTERM (K8s sends it on pod terminate, ECS on task stop):
- Stop reporting readiness so the LB removes the pod from rotation.
- Wait for in-flight requests to finish (with a hard cap, say 30s).
- Close the DB pool, queue connections.
- Exit.
ASGI / FastAPI / Uvicorn does most of this when you wire lifespan correctly. The classic bug: app exits before in-flight requests complete → 502s in the LB metrics.
19. What’s the saga vs outbox split — same thing?
They solve different problems but compose:
- Saga — coordinates a multi-step business process across services with compensations on failure.
- Outbox — atomically write a domain event alongside the local DB transaction so the event can be reliably published.
A typical orchestrated saga uses outbox internally: each step ends with the service writing its event to the outbox, the relay publishes, the orchestrator reacts.
20. How do you stop a microservices design from becoming a distributed monolith?
Symptoms of distributed monolith:
- Services can’t deploy independently (one release requires coordinated deploys of three services).
- A shared “common” library that every service pins to the same version.
- Tight synchronous chains across services.
- Direct DB access into another service’s tables.
- “We need an end-to-end test environment to validate any change.”
Fixes:
- Independent versioning per service; tolerate version skew.
- Async events where possible.
- Schema/contract testing instead of E2E.
- One DB per service; cross-service data via API/events only.
- Resist the urge to share business logic across services — duplicate before you couple.
Mental model
| Layer | Concern |
|---|---|
| Gateway | end-user auth, rate limits, TLS, routing |
| Mesh | service identity (mTLS), retries, observability |
| Service | business logic, owns its DB |
| Saga / outbox | cross-service consistency |
| Tracing | trace IDs propagated across hops, sampled at the collector |
| Idempotency | every consumer dedupes; every POST has a key |
When in doubt: start with a modular monolith. Split only when there’s a concrete reason (independent scaling, team autonomy, deploy independence) — not because “microservices.”
Interview angle
- “When should you not use microservices?” - at the start. A modular monolith gives you clear boundaries without distributed transactions, network failure, versioning and deployment overhead. Split when team scaling or genuinely divergent scaling needs demand it.
- “How do you decide service boundaries?” - by bounded context and rate of change, not by technical layer. A boundary that requires a synchronous call for every operation is in the wrong place.
- “How do you keep data consistent across services?” - you don’t get distributed transactions. Use sagas with compensating actions and the transactional outbox for reliable event publication, and accept eventual consistency where the domain allows it.
- “What’s the cost people underestimate?” - operational. Every service needs deployment, monitoring, alerting, on-call and dependency management, and debugging spans several of them, which is why distributed tracing stops being optional.