The 50 microservices questions interviewers actually ask, with direct answers, real code and config, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
50 questions with answersKey Takeaways
Microservices is an architecture style that builds an application as a collection of small services, each running in its own process and owning one business capability, like payments, search, or user accounts. Services are deployed independently and communicate over the network, usually through HTTP or messaging. As Chris Richardson's microservices.io defines the pattern, each service is independently deployable and organized around a business capability, with its own data. That last part matters: a service owns its database and no other service reaches into it directly, which is what lets teams work and ship without stepping on each other. The trade is real complexity. What was an in-process function call becomes a network call that can time out, data that used to live in one transaction now spans services, and a single request may touch a dozen processes. Interviews probe how you reason about those trade-offs: when a monolith is the right call, how you keep data consistent across service boundaries, how one slow service doesn't take the whole system down, and how you'd debug a request that failed three hops deep. This page collects the 50 questions that come up most, each with a direct answer plus code where a candidate would actually show one. Pair it with our AI interview preparation guides for the live-interview format, since the first backend round is increasingly an AI-driven technical screen.
Watch: Microservices explained - the What, Why and How?
Video: Microservices explained - the What, Why and How? (TechWorld with Nana, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your Microservices certificate.
The fundamentals every entry-level round checks: what microservices are, how they differ from a monolith, how services communicate, and the core building blocks. If any answer here surprises you, that's your study list.
Microservices is an architecture style where an application is built as a set of small, independently deployable services. Each service runs in its own process, owns one business capability like payments or search, and communicates with others over the network, usually via HTTP or messaging.
The point is independence: teams can build, deploy, and scale a service without coordinating a full-app release. That autonomy is the main draw, and it comes with real costs, network calls, distributed data, and harder debugging, which is what the rest of an interview probes.
Key point: Lead with 'small, independently deployable services, each owning a business capability'. Interviewers open with this to hear a crisp definition, not a list of tools.
Watch a deeper explanation
Video: What is a MICROSERVICE ARCHITECTURE and what are its advantages? (Gaurav Sen, YouTube)
A monolith is one deployable unit with one shared codebase and usually one database. Everything ships together, so a change to any part means redeploying the whole app. It's simpler to build, test, and debug, which is why it's the right start for most products.
Microservices split that same app into separate services, each independently deployable with its own database. You can deploy and scale one piece without touching the rest, at the cost of network calls between services and data that spans them. The honest coverage names both the gain and the cost.
| Monolith | Microservices | |
|---|---|---|
| Deployment | One unit, all together | Independent per service |
| Data | One shared database | Database per service |
| Scaling | Whole app at once | Each service separately |
| Debugging | In-process, simpler | Distributed, harder |
Key point: Don't frame microservices as strictly better. Saying 'monolith first, split when a real pressure justifies it' signals judgment, which is what this question checks.
Watch a deeper explanation
Video: Monolithic vs Microservice Architecture: Which To Use and When? (Alex Hyett, YouTube)
Independent deployment is the big one: teams ship their own service on their own schedule without a coordinated full-app release. Targeted scaling is next: you scale only the service under load instead of the whole app. And teams get autonomy, including freedom to pick the right tech for their service.
Fault isolation helps too: a well-designed system contains a failure in one service instead of taking everything down. These benefits are real, but each has a matching cost, so a strong answer pairs them with the trade-offs rather than selling microservices as free wins.
Key point: Pair every benefit with its cost. 'Independent deploys, but now you own network failure and distributed data' reads far more senior than a benefits-only pitch.
The core problem is that you've turned in-process function calls into network calls, which can be slow, time out, or fail. Data that used to sit in one database now spans services, so there's no single transaction and you deal with eventual consistency. Debugging is harder because one request crosses many processes.
On top of that comes operational overhead: many deployables to build, monitor, and secure; service discovery; distributed tracing; and more moving parts to fail. That's why the standard advice is not to microservices unless the organization and problem actually need them comes first.
Key point: Naming distributed data and 'no single transaction' as the hard part shows real understanding. Candidates who list only ops overhead miss the deeper challenge.
Two broad styles. Synchronous request/response, where a service calls another and waits for a reply, typically over REST/HTTP or gRPC. And asynchronous messaging, where a service publishes an event or a message to a broker (Kafka, RabbitMQ) and other services react on their own time, with no waiting.
Synchronous is simple and immediate but couples the two in time: if the callee is down, the caller fails. Asynchronous decouples them and handles bursts and outages better, at the cost of eventual consistency and more moving parts. Real systems mix both, matching each interaction to its needs.
Key point: Contrast synchronous and asynchronous clearly. The key line is 'sync couples them in time, async decouples them', which is exactly what the question is probing.
REST uses HTTP with JSON, is human-readable, and works everywhere, which makes it the default for public APIs and easy debugging. gRPC uses HTTP/2 with Protocol Buffers, a compact binary format, and generates typed client and server code from a shared contract, so it's faster and stricter.
Use REST for public-facing APIs and where readability and broad compatibility matter. Reach for gRPC for high-throughput internal service-to-service calls where latency and strong contracts matter, and where streaming helps. Many systems use gRPC internally and expose REST at the edge through a gateway.
| REST | gRPC | |
|---|---|---|
| Format | JSON, text | Protocol Buffers, binary |
| Transport | HTTP/1.1 or 2 | HTTP/2 |
| Best at | Public APIs, easy debugging | Fast internal calls, streaming |
| Contract | Loose (OpenAPI optional) | Strict, code-generated |
Key point: Say 'REST at the edge, gRPC internally' if asked to pick. It shows you match the protocol to the interaction rather than treating it as one-size-fits-all.
An API gateway is a single entry point that sits in front of your services. Clients call the gateway, and it routes each request to the right service. Without it, clients would need to know every service's address and call several of them directly, which is fragile and leaks internal structure.
It also centralizes cross-cutting concerns: authentication, rate limiting, TLS termination, request routing, and sometimes aggregating several service calls into one response for a client. The one thing to avoid is stuffing business logic into the gateway, which turns it into a new monolith and a bottleneck.
Key point: Warn against putting business logic in the gateway. Naming that anti-pattern shows you understand its proper, thin role, a common follow-up.
Because a shared database recouples the services you just split apart. If two services read and write the same tables, a schema change in one can break the other, and you can no longer deploy them independently, which was the whole point.
Database-per-service keeps each service's data private behind its API, so the service owns its schema and can change it freely. The cost is that data now spans services: no single join, no single transaction, and you handle consistency with patterns like sagas and events. That trade is the heart of the pattern.
Key point: Frame the shared database as an anti-pattern that reintroduces coupling. That reasoning, not just 'each service has a DB', is what earns the point.
Service instances start, stop, scale, and move, so their network addresses keep changing. Service discovery is how a caller finds a healthy instance of the service it needs without hardcoding IPs. Instances register themselves in a registry, and callers look them up by service name.
There's client-side discovery, where the caller queries the registry and picks an instance, and server-side discovery, where a load balancer or platform (like Kubernetes DNS and Services) does it for you. On most modern platforms, discovery is built in, so you call a stable service name and the platform resolves it.
Key point: Mention that platforms like Kubernetes give you discovery via stable Service names. Knowing it's often built in, not hand-rolled, indicates practical.
A stateless service keeps no client session data in its own memory between requests; any needed state lives in a shared store like a database or cache. That means any instance can handle any request, so you can add or remove instances freely behind a load balancer.
Statelessness is what makes horizontal scaling and self-healing work: if an instance dies, no session is lost, and a new instance serves traffic immediately. When state must be sticky, you push it out to Redis or a database rather than pinning users to a specific instance.
Key point: statelessness connects to 'any instance can serve any request'. That link to scaling and recovery is the reason interviewers ask it.
The twelve-factor app is a set of guidelines for building services that run well in the cloud: store config in the environment, treat backing services as attached resources, keep processes stateless and disposable, ship logs as event streams, and keep dev and prod as similar as possible, among others.
It matters because each microservice should follow these factors to be independently deployable and scalable. Config in the environment lets one image run in every stage; stateless disposable processes let the platform scale and restart them; logs as streams let a central system collect them. It's the practical backbone of a cloud-native service.
Key point: Naming two or three concrete factors (config in env, stateless processes, logs as streams) beats reciting 'there are twelve factors'. Specifics show you've applied it.
A bounded context comes from domain-driven design: it's a boundary within which a specific model and its terms have one clear meaning. 'Customer' in the billing context and 'customer' in the support context can be different models, and each context owns its own definition.
It helps because a good microservice usually maps to one bounded context: the service owns that model and its data. Drawing boundaries around business capabilities and contexts, rather than technical layers, is what keeps services cohesive and loosely coupled. Split by domain, not by 'all the database code'.
Key point: Say 'split by business capability, not technical layer'. bounded contexts from DDD matters.
Synchronous means the caller sends a request and waits for the response before doing anything else, like a normal REST call. The two services are coupled in time: if the callee is slow or down, the caller is stuck or fails.
Asynchronous means the caller sends a message or publishes an event and moves on without waiting; the other service processes it whenever it can, usually through a broker. This decouples them, absorbs traffic spikes, and survives short outages, but the result is eventual consistency and more complexity to reason about.
Key point: The one-line answer is 'sync waits and couples in time, async fires and forgets'. Stating the temporal coupling is what the question is really after.
A container packages a service with its exact dependencies into one portable, immutable image that runs the same on a laptop and in production. Since each microservice is small and independent, one container per service gives you clean isolation and independent deployment without VM overhead.
Containers also make scaling and self-healing cheap: an orchestrator like Kubernetes can run many replicas of a service, replace a crashed one in seconds, and roll out a new version gradually. The container is the deployable unit that matches the microservice's need to ship and scale on its own.
Key point: Link 'one container per service' to independent deployment and scaling. That's the connection the question is checking, not container internals.
Keep config out of the code and out of the image, and inject it at runtime as environment variables or mounted files, so the same image runs in every environment with different values. Secrets (passwords, tokens) go in a secrets manager, never in source control.
For settings shared across services or that change without a redeploy, a central config service or store (Spring Cloud Config, Consul, a cloud parameter store) holds the values and services read them. The rule is one image, environment-specific config, and sensitive values sourced from a real secrets manager.
# same image, config injected at runtime
env:
- name: DB_HOST
value: orders-db
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: orders-secrets
key: db-passwordKey point: State 'config in the environment, secrets in a manager, one image everywhere'. That twelve-factor framing is exactly what a strong answer sounds like.
Load balancing spreads incoming requests across the multiple instances of a service so no single instance is overwhelmed, and it removes unhealthy instances from rotation using health checks. Since each service usually runs several replicas for capacity and redundancy, something has to decide which replica gets each request.
It happens at more than one layer: an edge load balancer routes external traffic to the cluster, and inside the cluster, service-to-service calls are balanced too, often by the platform (Kubernetes Services) or a service mesh. Because services are stateless, any replica can serve any request, which is what makes balancing across them safe.
Key point: load balancing connects to statelessness: 'any replica can serve any request'. That link, plus 'it happens at multiple layers', is what the question checks.
Polyglot programming means different services can be written in different languages, and polyglot persistence means each can use the database that fits its job: a relational store for orders, a search engine for search, a key-value store for sessions. Because each service is independent and owns its data, these choices are per-service, not system-wide.
The upside is picking the right tool for each capability. The downside is sprawl: every extra language and database is one more thing to operate, monitor, patch, and hire for. Mature teams allow polyglot where it clearly pays off but set sensible defaults so the system doesn't fragment into a dozen stacks nobody can maintain.
Key point: Pair the 'right tool per service' benefit with the operational sprawl cost. Endorsing unlimited polyglot without that caveat indicates inexperienced.
For candidates with working experience: resilience patterns, data consistency across services, messaging, and the judgment questions that separate pattern-namers from people who've run these systems.
A circuit breaker wraps calls to a dependency and tracks failures. When failures cross a threshold, it 'opens' and fails fast for a cool-down window instead of sending more calls to a service that's clearly struggling, then it lets a few test calls through to see if the dependency recovered before closing again.
It's needed to stop cascading failure. Without it, callers keep hammering a slow service, threads pile up waiting on timeouts, and the caller runs out of resources and fails too, which spreads upstream. The breaker contains the damage and lets you return a fallback while the dependency recovers.
Key point: Describe all three states (closed, open, half-open) and the cascading-failure it prevents. That full picture is what separates a real answer from a definition.
Always set a timeout on a network call so a slow dependency can't make the caller hang and exhaust its threads. Then retry transient failures a small number of times with exponential backoff and jitter, so you don't stampede a recovering service with synchronized retries.
The catch is that retries are only safe for idempotent operations. Retrying a payment or an order-create without an idempotency key can double the effect. So you pair retries with idempotency, cap the attempts, and back them with a circuit breaker and a fallback for when the dependency stays down.
Handling a call to a flaky dependency
Retries only help transient failures, and only for idempotent operations. Retrying a non-idempotent write can duplicate the effect.
Key point: Say retries are safe only for idempotent calls, and mention backoff with jitter. Blindly retrying everything is the mistake this question hunts for.
An operation is idempotent if doing it many times has the same result as doing it once. Reading data is naturally idempotent; creating an order usually isn't, unless you design it to be. In a network where messages get retried and delivered more than once, idempotency is what keeps retries safe.
The common technique is an idempotency key: the client sends a unique key with the request, the service records it, and if the same key arrives again it returns the original result instead of doing the work twice. That's how you avoid charging a card twice when a timeout triggers a retry.
POST /payments
Idempotency-Key: 8f3a1c9e-2b47-4d10-9a6f-11c2e0b7d4aa
Content-Type: application/json
{ "amount": 4999, "currency": "usd" }
# Same key replayed -> service returns the original result, no second chargeKey point: idempotency keys matters.
You can't use one database transaction across services because each owns its own database. The standard answer is the Saga pattern: model the workflow as a sequence of local transactions, one per service, and if a later step fails, run compensating actions to undo the earlier ones.
Sagas come in two flavors: choreography, where each service emits events that trigger the next step, good for simple flows; and orchestration, where a central coordinator tells each service what to do and handles compensation, clearer for complex flows. Either way you accept eventual consistency instead of a global lock.
Key point: The Saga pattern and 'compensating transactions', then distinguish choreography from orchestration. That depth is exactly what the intermediate tier tests.
Eventual consistency means that after a change, different services may briefly hold different views of the data, but they converge to the same state given a little time. It's the trade you accept when data spans services and you can't wrap everything in one transaction.
It's acceptable when a short delay doesn't harm the business: an order confirmation email, a search index update, a recommendation refresh. It's not acceptable where users need to see a change immediately and correctly, like an account balance during a transfer, where you keep that within one service and one transaction.
Key point: Give a concrete 'fine here, not fine there' pair. Showing you know where strong consistency is still required is more convincing than defining the term.
A message broker (Kafka, RabbitMQ) sits between services so a producer can publish a message or event without knowing or waiting for the consumers. It buffers messages, so a spike or a temporarily down consumer doesn't lose work, and it decouples producers from consumers in both time and identity.
You use one when you want asynchronous, event-driven communication: broadcasting that 'an order was placed' to several interested services, smoothing traffic bursts, or decoupling a slow downstream task from the request path. The cost is another critical piece of infrastructure to run and reason about.
Key point: Stress that the broker decouples producer and consumer 'in time and identity'. That framing shows you understand why async messaging is more than just queues.
In event-driven architecture, services communicate by producing and reacting to events, facts about something that happened, like 'OrderPlaced' or 'PaymentFailed', rather than calling each other directly. A service publishes an event, and any interested service consumes it and does its own work.
The win is loose coupling: the publisher doesn't know who listens, so you add new consumers without touching it. The trade is that the overall flow is spread across services and events, which makes it harder to follow and debug, so you lean on tracing, good event schemas, and clear ownership of each event.
Key point: Contrast 'publish a fact, whoever cares reacts' with direct calls. The loose-coupling benefit and the harder-to-trace cost are both worth naming.
Most brokers guarantee at-least-once delivery: a message will be delivered, but possibly more than once, because of retries and redelivery after a consumer crash. Exactly-once is very hard and often more than you need, so you design assuming duplicates will arrive.
You cope by making consumers idempotent: track processed message IDs and skip a message you've already handled, or design the operation so reprocessing is harmless. That way a duplicate delivery doesn't cause a double side effect. Idempotent consumers are what make at-least-once safe in practice.
Key point: Say 'assume duplicates, make the consumer idempotent'. Reaching for exactly-once instead of idempotent consumers is a common intermediate-level miss.
It solves the dual-write problem: a service that needs to update its database and publish an event can't do both atomically, so a crash between them either loses the event or publishes an event for a change that rolled back. Either way, services drift out of sync.
The outbox pattern writes the event into an 'outbox' table in the same local transaction as the business change, so they commit together or not at all. A separate process then reads the outbox and publishes the events to the broker, retrying until they're delivered. One atomic write, reliable eventual publishing.
Key point: Framing it as 'the dual-write problem' shows you understand why you can't just save then publish. That's the insight the question is testing.
Prefer backward-compatible changes so you don't need a new version at all: add optional fields, add endpoints, never remove or repurpose existing fields while clients still use them. Most changes can be additive if you design responses to tolerate extra fields.
When a breaking change is unavoidable, expose a new version (a /v2 path or a version header) and run old and new side by side long enough for clients to migrate, then retire the old one. The key rule for independent deployment is: a service and its clients must be able to deploy separately, so you can't break the contract in one step.
Key point: Lead with 'make changes additive and backward-compatible'. Jumping straight to /v2 for every change signals you haven't managed a real evolving API.
Ship logs off each host to a central store (an ELK/Opensearch stack, Loki, or a cloud log service) so you can search across all services in one place, since a single request's evidence is scattered across many processes. Emit structured logs (JSON with consistent fields) so they're queryable, not just text.
The detail that makes it useful is a correlation ID (or trace ID) attached to every log line and propagated across service calls, so you can pull up every log for one request across every service it touched. Without that ID, centralized logs are a pile you can't stitch back into a story.
Key point: The correlation ID is the whole answer. structured logs plus a propagated trace ID is what matters.
Distributed tracing follows one request across every service it touches. Each service records spans (units of work) tagged with a shared trace ID that propagates through headers, and the spans assemble into a waterfall showing where time was spent and which hop failed.
It answers the question logs and metrics can't: when a request is slow or broken, metrics say something's wrong and logs give scattered detail, but only a trace shows the actual path, the third database call or a slow downstream API. OpenTelemetry is the vendor-neutral standard for instrumenting it, and context propagation across boundaries is the part teams get wrong.
Key point: The OpenTelemetry and 'context propagation across service boundaries'. Both show you've instrumented tracing, not just looked at a dashboard.
A health check is an endpoint a service exposes so the platform can tell whether it's alive and whether it's ready for traffic. Liveness answers 'is the process healthy or should it be restarted', and readiness answers 'can it serve requests right now', for example after it's connected to its database.
An orchestrator like Kubernetes uses them to route traffic only to ready instances and to restart dead ones automatically. Getting readiness right prevents sending requests to an instance that's still warming up; getting liveness right recovers a hung instance without a human. They're what makes self-healing and zero-downtime rollout work.
livenessProbe:
httpGet:
path: /health/live
port: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
periodSeconds: 5Key point: The line 'readiness gates traffic, liveness gates restarts' is the precise answer. Conflating the two is the common slip here.
A BFF is a dedicated backend layer per client type, one for the web app, one for mobile, that sits between the clients and the microservices. Instead of every client calling many services and stitching responses together, the BFF does that aggregation and shapes the data exactly the way that client needs.
It helps because a mobile app and a web app have different data and payload needs, and pushing that shaping into a shared gateway or into every client gets messy. The BFF keeps client-specific logic in one place per client, at the cost of another service to maintain per frontend.
Key point: Explain the 'one backend per client type' idea and why one-size API gateways get awkward. That reasoning is what the question is checking.
Incrementally, using the strangler fig pattern rather than a big-bang rewrite. You put a routing layer in front of the monolith, then carve out one capability at a time into a new service and route just that traffic to it, leaving everything else on the monolith. Over time the new services grow around the old system until it can be retired.
You a piece that has clear boundaries and real value in being separate, and you extract its data along with it, since database-per-service is the hard part comes first. A big-bang rewrite is the classic failure: it's risky, slow, and you're building new while the old system keeps changing under you.
Key point: The strangler fig pattern and reject the big-bang rewrite explicitly. Interviewers ask migration questions largely to see if you avoid that trap.
The bulkhead pattern isolates resources so a failure in one part can't sink the whole service, named after the watertight compartments in a ship. In a service, that means giving each downstream dependency its own pool of threads or connections, so if one dependency goes slow, only its pool fills up and other work keeps flowing.
Without bulkheads, a single slow dependency can consume all of a service's threads as calls back up waiting on timeouts, and then the service can't serve any request, even ones that don't touch the slow dependency. Bulkheads plus timeouts and circuit breakers are the standard trio for containing failure.
Key point: The ship analogy plus 'separate resource pools per dependency' is the answer. Pairing it with timeouts and circuit breakers shows you see the whole resilience toolkit.
advanced rounds probe architecture, data, security, and production scars. Expect every answer here to draw a follow-up about trade-offs and failure modes.
Draw boundaries around business capabilities and bounded contexts, not technical layers. A service should own one cohesive piece of the domain (orders, inventory, billing) end to end, including its data. The test is whether the service can change and deploy without constantly needing coordinated changes in others; if it can't, the boundary is wrong.
Look at how the domain and teams actually work, high cohesion inside a service, loose coupling between services, and align with team ownership so one team owns a service. Getting boundaries wrong is the most expensive mistake in microservices: too fine and you get a distributed monolith of chatty services; too coarse and you're back to a monolith. When unsure, err toward fewer, larger services and split later.
Key point: Say 'wrong boundaries are the most expensive mistake, so start coarse and split later'. That caution is the production signal; eager over-splitting is the red flag.
Watch a deeper explanation
Video: Mastering Chaos - A Netflix Guide to Microservices (InfoQ, YouTube)
A distributed monolith is the worst of both worlds: services split apart physically but still so tightly coupled that they must be deployed together and can't be understood in isolation. You pay the full cost of distribution, network calls, latency, operational overhead, without the benefit of independent deployment.
The tells are services that share a database, a change in one that forces coordinated releases across several, and chatty synchronous call chains where one request fans out through many services. You avoid it by enforcing database-per-service, keeping communication loose (async events where possible), designing around business capabilities, and treating 'can I deploy this service alone' as the acid test.
Key point: Naming the shared database and lockstep deploys as the symptoms shows you've seen this fail. The acid test, 'can I deploy one service alone', is the answer to lead with.
CAP says that when a network partition happens between nodes, a distributed system must choose between consistency (every read sees the latest write) and availability (every request gets a response). You can't have both during a partition, and partitions are a fact of life in a distributed system.
In practice most microservices lean toward availability and accept eventual consistency, staying responsive and reconciling data shortly after, because a brief stale read usually beats an outage. But for money-critical paths you choose consistency and refuse to serve rather than serve wrong. The senior point is that it's a per-use-case choice, not one global setting.
Key point: Say the CA-vs-AP choice is made per data flow, not once for the whole system. That nuance beats reciting the three letters.
Accept that a single ACID transaction can't span services and design for it. Keep anything that truly must be atomic inside one service and one database. For workflows that cross services, use sagas with compensating actions and the transactional outbox so state changes and the events announcing them commit together.
For read-side consistency, use events to propagate changes and let each service keep its own local copy (or read model) of the data it needs, updated as events arrive. Add idempotent consumers so duplicate events don't corrupt state, and make eventual consistency visible in the UX where a delay is possible, rather than pretending everything is instant.
Key point: Tie together sagas, outbox, idempotent consumers, and local read models. Naming how those pieces combine is what separates production-ready answers from pattern trivia.
CQRS (Command Query Responsibility Segregation) splits the write model from the read model. Commands that change state go through one model optimized for correctness and business rules; queries go through separate read models, often denormalized and tuned for fast reads, kept up to date from events the write side emits.
It's worth it when reads and writes have very different shapes or scale, when you need multiple specialized read views, or alongside event sourcing. It's overkill for a simple CRUD service, where it just adds two models and eventual consistency between them for no real gain. Reach for it when the read/write mismatch is a real problem, not by default.
Key point: Being able to say 'CQRS is overkill for simple CRUD' is the signal. Applying it everywhere is a maturity red flag interviewers watch for.
Event sourcing stores state as an append-only log of events rather than as the current values. Instead of overwriting a row, you record 'MoneyDeposited', 'MoneyWithdrawn', and derive the current balance by replaying events. The event log becomes the source of truth, and you can rebuild any past state or new read models from it.
The upsides are a full audit trail, time travel, and easy new projections. The costs are real: querying current state means replaying or maintaining projections, schema evolution of old events is tricky, and the mental model is unfamiliar to most teams. It pairs with CQRS but adds significant complexity, so it fits domains that genuinely need the history, like finance or auditing, more than a typical CRUD service.
Key point: The audit-trail benefit and the query/schema-evolution costs. Recommending event sourcing only where history is genuinely needed indicates experienced.
A service mesh (Istio, Linkerd) puts a sidecar proxy next to each service to handle service-to-service concerns uniformly: mutual TLS, retries, timeouts, traffic splitting for canaries, and rich telemetry, all without changing application code. A control plane configures every proxy centrally.
It's worth it once you have many services and need consistent mTLS everywhere, fine-grained traffic control, and per-hop observability you don't want to reimplement in each service. It's overkill for a handful of services, where the added latency, resource cost, and operational burden outweigh the benefit. Linkerd is the lighter option when you want mesh basics without Istio's weight.
Key point: As with microservices themselves, saying 'not worth it yet' for a small system is the mature answer. Reflexively adding Istio is a red flag.
zero trust inside the cluster: don't assume the network is safe comes first. Encrypt service-to-service traffic with mutual TLS so both sides authenticate, often handled by a service mesh so you don't hand-roll certs in every service. Give each service its own identity and scope its permissions to the minimum it needs.
For request authorization, authenticate the user once at the edge (the gateway validates the token), then propagate a signed token (a JWT) so downstream services can verify who's calling and what they're allowed to do, rather than re-authenticating from scratch. Add secrets in a manager, short-lived credentials, and network policies that restrict which services can talk to which.
Key point: Lead with 'zero trust and mTLS between services', then edge auth with propagated tokens. Assuming the internal network is safe is the mistake this question hunts.
Authenticate once at the edge: the API gateway or an auth service validates the user's credentials and issues a token (typically a signed JWT or an opaque token backed by an introspection endpoint). That token travels with the request to downstream services, which verify it rather than asking the user to log in again at every hop.
For authorization, keep the decision close to the resource: each service enforces what its own data allows, using claims in the token (roles, scopes) plus its own rules, rather than trusting the gateway alone. Centralize the policy where you can (an authorization service or policy engine like OPA) so rules stay consistent, and keep tokens short-lived to limit the blast radius of a leak.
Key point: Split it cleanly: authenticate at the edge, authorize at each service. Saying 'the gateway alone decides everything' misses that services must enforce their own access.
Use a testing pyramid adapted for distribution. Lots of fast unit tests inside each service, then integration tests for a service with its real database and mocked collaborators. The tricky part is the boundaries, so add contract tests (consumer-driven, with a tool like Pact) that verify a provider still satisfies what its consumers expect, without spinning up the whole system.
Keep end-to-end tests that exercise the full running system few and targeted, because they're slow, flaky, and expensive to maintain across many services. Contract tests are what let you deploy a service independently with confidence: they catch a breaking API change against every known consumer before it ships.
Key point: Bringing up consumer-driven contract testing is the strong move. It's how you keep independent deploys safe without brittle end-to-end suites.
Cover the three pillars and tie them together. Metrics (request rate, error rate, latency percentiles per service) to notice a problem and drive alerts; distributed traces to follow a request across services and localize the slow or failing hop; and structured logs with a correlation ID for the exact detail. The correlation or trace ID linking all three is what makes them useful together.
Then alert on symptoms and user impact (SLO burn, rising error rate) rather than raw resource metrics, and standardize instrumentation with OpenTelemetry so every service reports the same way. The goal is to answer 'which service, which call, why' for a novel failure, not just to have dashboards.
Key point: Give the workflow, metrics to detect, traces to localize, logs for detail, and mention alerting on symptoms. That shows you've operated the system, not just defined the pillars.
The failure mode to design against is cascading failure: one slow service makes its callers block on it, their threads pile up, and they fail too, propagating upstream until the whole system is down. The defenses combine: strict timeouts so no call hangs, circuit breakers to fail fast when a dependency is clearly broken, and bulkheads that isolate resource pools so one struggling dependency can't consume all the caller's threads.
Add graceful degradation, return cached or default data when a non-critical dependency is down instead of failing the whole request, and load shedding to reject excess traffic early rather than collapsing. The mindset is that dependencies will fail, so every remote call is wrapped in a timeout, a breaker, and a fallback plan.
Key point: The timeouts, circuit breakers, bulkheads, and graceful degradation together. Any one alone is incomplete; the combination is what The production-ready answer covers.
Rate limiting caps how many requests a client can make in a window, to protect services from abuse, runaway clients, and traffic spikes. Put it at the edge in the API gateway for per-client and per-endpoint limits, since that's the single entry point, and add limits inside services for internal protection of expensive operations.
Common algorithms are token bucket (allows bursts up to a bucket size, then a steady refill rate) and sliding window (smoother, counts requests over a moving time range). In a multi-instance system the counter has to be shared (in Redis, say) so the limit holds across all instances, not per instance. Return a clear 429 with a Retry-After so clients back off correctly.
Key point: Mention that the counter must be shared across instances (e.g. Redis). Per-instance rate limits that don't actually enforce the global limit is a subtle bug interviewers probe.
Rolling replaces instances gradually with no downtime, but a bad version reaches everyone as it ramps. Blue-green runs two full environments and flips all traffic at once, giving instant rollback but doubling infrastructure during the switch. Canary sends a small traffic slice to the new version first, watches metrics, and ramps only if it's healthy, giving the smallest exposure.
For microservices, canary plus good observability is the common choice because you can release one service at a time and catch a regression before it spreads. Feature flags are the finer-grained complement: ship code dark and toggle it on for a subset independent of the deploy, which separates 'deploy the code' from 'release the feature'.
Rollout strategies: blast radius if the new version is bad
Lower means fewer users hit a broken version before you catch it. Recreate has downtime and exposes everyone at once.
| Strategy | Downtime | Rollback speed | Exposure if bad |
|---|---|---|---|
| Recreate | Yes | Redeploy old | Everyone at once |
| Rolling | No | Roll out previous | Grows as it ramps |
| Blue-green | No | Instant (flip back) | Everyone at switch |
| Canary | No | Stop the ramp | Small slice first |
Key point: Bring up feature flags as separating deploy from release. That distinction, plus preferring canary for per-service rollout, is the mature answer.
Clarify first: expected scale, which steps must be strongly consistent (payment) versus eventually consistent (email, recommendations), and where the hard reliability requirements are. Then propose services by business capability: Catalog, Cart, Order, Payment, Inventory, Shipping, Notification, each owning its own data. Clients enter through an API gateway; the Order service coordinates the checkout.
For the cross-service checkout, use a saga: Order creates a pending order, Payment charges (idempotent, with an idempotency key), Inventory reserves stock, and if any step fails, compensating actions release the reservation and refund. Emit events ('OrderPlaced', 'PaymentCaptured') for Notification and analytics to consume asynchronously. Wrap synchronous calls in timeouts and circuit breakers, make consumers idempotent for at-least-once delivery, and add tracing across the flow. The structure that scores is clarify, boundaries by capability, saga for the transaction, events for the rest, resilience and observability throughout.
Key point: Open with clarifying questions and split by business capability, not layers. Jumping straight to a service list without asking about consistency and scale is the classic way to underperform on a design prompt.
Watch a deeper explanation
Video: What are Microservices? (IBM Technology, YouTube)
Splitting too early and too fine is the biggest one: teams adopt microservices for a small product before there's any team-scale or scaling pressure to justify it, and pay all the distribution cost for none of the benefit. Wrong boundaries are next, services carved by technical layer instead of business capability, which produces chatty, coupled services that must deploy together.
The other repeat offenders: a shared database that recouples everything, ignoring failure so one slow service cascades into an outage, no distributed tracing so a broken request is impossible to debug, and no investment in the operational platform (CI/CD, observability, deployment) that microservices demand. The pattern is treating microservices as a default rather than a deliberate trade made when the organization actually needs it.
Key point: Leading with 'splitting too early' and wrong boundaries shows you've seen these projects fail. Framing microservices as a deliberate trade, not a default, is the production signal.
Watch a deeper explanation
Video: What Are Microservices Really All About? (And When Not To Use It) (ByteByteGo, YouTube)
A monolith ships as one deployable unit with one shared database; microservices split the same app into independently deployable services, each with its own data. The monolith is simpler to build, test, and debug, and it's the right starting point for most new products. Microservices earn their complexity when independent teams need to deploy and scale parts of the system separately, when different pieces have very different scaling or technology needs, and when the codebase and org have grown too large to move fast as one unit. A modular monolith sits in between: one deployment, but strict internal boundaries you can split later. Knowing where each fits, and saying the trade-off out loud, is itself an interview signal.
| Aspect | Monolith | Modular monolith | Microservices |
|---|---|---|---|
| Deployment | One unit, deploy together | One unit, clear internal modules | Many units, deploy independently |
| Data | One shared database | One database, module-owned schemas | Database per service |
| Scaling | Scale the whole app | Scale the whole app | Scale each service on its own |
| Best fit | New products, small teams | Growing app, one team, future split | Many teams, differing scale needs |
| Main cost | Hard to scale teams and parts separately | Discipline to keep boundaries clean | Network, distributed data, ops overhead |
Prepare in layers, and trade-offs out loud is the explanation path. Most microservices rounds move from definition and boundary questions to a design or scenario task to a failure-mode discussion, so rehearse each stage rather than only reading answers.
The typical microservices interview flow
Earlier rounds increasingly run as AI-driven technical interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Microservices questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works