Microservices system design interviews are not about simply drawing multiple boxes and connecting them with arrows.
For senior Java and Spring Boot developers, interviewers want to understand how you make architectural decisions when the system needs to scale, handle failures, maintain data consistency, remain observable, and continue operating when dependencies become unavailable.
This guide covers 25 microservices system design interview questions and answers based on the problems engineers commonly face when designing production systems.
The questions cover:
If you are preparing for a Senior Java Developer, Senior Backend Developer, Software Architect or Microservices Architect interview, these are the areas where system design discussions usually become much deeper.
I would begin with business capabilities rather than creating services around technical layers.
A typical e-commerce architecture could contain:
The high-level architecture could look like this:
Web / Mobile Clients
|
v
+-------------+
| API Gateway |
+------+------+
|
+-------------------+-------------------+
| | | | |
v v v v v
Product Customer Order Payment Inventory
Service Service Service Service Service
|
v
Kafka
|
v
NotificationEach service should own its business logic and data. Synchronous REST or gRPC can be used where an immediate response is required, while Kafka or another message broker can handle asynchronous workflows.
Redis can be used for appropriate caching scenarios, and Kubernetes can provide container orchestration and horizontal scaling.
Senior-level consideration: The objective is not to create the maximum number of services. The services should have clear ownership, meaningful boundaries and independent scaling requirements.
I would start with business capabilities and bounded contexts.
For example, Order, Payment and Inventory represent different business responsibilities and usually have different data ownership and transaction boundaries.
I would evaluate:
A warning sign is when two supposedly independent services constantly communicate with each other and must always be deployed together.
That may indicate that the service boundary is too fine-grained or incorrectly defined.
Interview tip: Avoid saying that every database table should become a microservice. Service boundaries should follow business capabilities, not tables.
I would make the decision based on the business requirement.
Synchronous communication is useful when the caller needs an immediate response.
Client
|
v
Order Service
|
v
Product Service
|
v
Immediate ResponseAsynchronous messaging is useful when processing can happen independently.
Order Service
|
| OrderCreated
v
Kafka
/ \
Payment NotificationFor example, after an order is created, Notification does not necessarily need to block the customer's order request.
Using asynchronous messaging can reduce coupling and allow consumers to process events independently.
I would normally use REST or gRPC for synchronous interactions and Kafka or another message broker for asynchronous communication.
Every synchronous dependency should have an explicit timeout.
Depending on the failure characteristics, I would also consider:
A critical senior-level principle is that a remote service call should never be treated like a local method call.
A local method may take milliseconds. A remote call can fail, become slow, lose connectivity, or return an error.
A cascading failure occurs when one unhealthy service causes other services to become unhealthy.
Consider:
Order Service
|
v
Payment Service
|
v
Bank API
X
Very SlowIf Payment waits indefinitely, Order threads can become blocked. Eventually the Order Service itself may become unavailable.
I would use:
For non-critical dependencies, the application should continue operating with reduced functionality when possible.
The API Gateway acts as the entry point for external clients.
Typical responsibilities include:
For example:
Mobile App
|
v
API Gateway
|
+---- Product Service
|
+---- Order Service
|
+---- Customer Service
|
+---- Payment ServiceThe gateway should not contain large amounts of business logic. Otherwise, it can become a centralized bottleneck or a distributed monolith.
Service discovery allows services to locate other service instances without hard-coded IP addresses.
In Kubernetes, Services and DNS provide a common mechanism.
order-service
payment-service
inventory-serviceThe Order Service can communicate with Payment using the Kubernetes service name instead of knowing the IP address of a specific pod.
In non-Kubernetes environments, a service registry can be used.
Senior-level consideration: Service discovery also needs to account for instance health, load balancing and the lifecycle of service instances.
I would generally avoid trying to extend a traditional database transaction across independent microservices.
Instead, I would model the business workflow using local transactions and events.
Order Created
|
v
Payment Completed
|
v
Inventory Reserved
|
v
Order ConfirmedIf a later step fails, the system can execute a compensating action.
This is one of the common use cases for the Saga pattern.
The Saga pattern manages a distributed business transaction as a sequence of local transactions.
For example:
1. Create Order
|
2. Process Payment
|
3. Reserve Inventory
|
4. Confirm OrderIf inventory reservation fails after payment succeeds, a compensating action can refund or cancel the payment.
There are two common Saga approaches.
Choreography: Services react to events generated by other services.
Orchestration: A coordinator controls the workflow and tells individual services what to do.
For complex business workflows, orchestration can make the overall process easier to understand and monitor.
Each service should normally own its data.
Instead of directly modifying another service's database, services communicate through APIs or events.
Order Service
|
| OrderCreated
v
Kafka
/ \
/ \
Payment InventoryFor important database-plus-event workflows, the Transactional Outbox Pattern can help ensure that a database change and its corresponding event are reliably coordinated.
Eventual consistency is often more appropriate than trying to force a distributed ACID transaction across multiple services.
An idempotent API safely handles repeated requests without creating unintended duplicate effects.
This is particularly important for payment and order APIs.
POST /payments
Idempotency-Key: payment-12345The service stores the idempotency key and the result.
If the same request arrives again, the service can return the existing result instead of executing the payment again.
This protects against client retries, network failures and duplicate requests.
I would assume that duplicate delivery can happen and design consumers to be idempotent.
For example, a consumer can store processed event IDs:
event_id
--------
EVT1001
EVT1002
EVT1003Before processing an event, the consumer checks whether it has already processed that event.
Database uniqueness constraints can provide an additional protection layer.
Important: Do not design an event-driven system assuming that every message will be delivered exactly once in every failure scenario. Business operations should still be protected against duplicates.
These mechanisms solve different failure scenarios.
| Mechanism | Purpose |
|---|---|
| Timeout | Prevents waiting indefinitely for a dependency. |
| Retry | Attempts transient failures again. |
| Circuit Breaker | Stops repeatedly calling an unhealthy dependency. |
| Bulkhead | Isolates resources between dependencies. |
Retries should be bounded and normally use exponential backoff with jitter.
Blindly retrying every failure can create a retry storm and make an outage worse.
First, prevent requests from waiting indefinitely by using timeouts.
Then use a circuit breaker to stop continuously sending traffic to the unhealthy service.
For non-critical functionality, graceful degradation can be used.
Product Details → Available
Recommendations → Temporarily UnavailableThe customer can still view and purchase the product.
For critical operations, work may need to be persisted and processed asynchronously after the dependency recovers.
I would first identify read-heavy data that does not change on every request.
A local cache such as Caffeine can be useful when data can safely be cached inside an application instance.
Redis can be useful when multiple service instances need a shared cache.
The caching design should define:
Before introducing a cache, I would also investigate the underlying database query and access pattern.
A common architecture uses OAuth 2.0 and OpenID Connect for identity, with JWT access tokens used for API authorization.
The API Gateway can perform initial authentication checks, but individual services should still enforce authorization for resources they own.
Services should validate relevant token properties such as:
Authentication answers "Who are you?" while authorization answers "What are you allowed to do?"
I would use distributed tracing with trace IDs and span IDs.
Client
|
v
API Gateway
|
v
Order Service
|
v
Payment Service
|
v
Inventory ServiceThe trace shows the time spent in each service and dependency.
OpenTelemetry can be used to instrument services and propagate trace context across service boundaries.
This makes it easier to determine whether latency comes from the application, database, network or another downstream service.
I would combine metrics, logs and distributed traces.
Important metrics include:
Logs should be structured and include correlation or trace identifiers.
Tracing should connect operations across service boundaries.
The objective is not simply to collect logs. The system should allow an engineer to answer questions such as:
I would first optimize the existing database workload.
That includes:
Depending on the workload, further techniques can include read replicas, partitioning and sharding.
Each service should ideally own its database boundary instead of allowing multiple services to directly modify the same tables.
I would combine horizontal scaling with caching, rate limiting, backpressure and asynchronous processing.
Users
|
v
API Gateway
|
v
Rate Limiter
|
v
Service Instances
|
v
Message Queue
|
v
WorkersKubernetes can horizontally scale service instances based on appropriate metrics.
Caching can reduce database traffic, while queues can absorb bursts of expensive asynchronous work.
Rate limiting protects the system from traffic that exceeds its safe processing capacity.
I would use rolling deployments or another controlled deployment strategy with readiness checks and graceful shutdown.
Database changes should also be backward compatible.
A common approach is the expand-and-contract pattern:
This prevents application and database changes from becoming tightly coupled.
Prefer additive changes whenever possible.
For example, adding an optional response field is generally less disruptive than removing an existing field.
For breaking changes, introduce a new API version or contract and give consumers sufficient time to migrate.
Contract testing can help identify breaking changes before deployment.
Payment systems require particularly strong idempotency because repeating a payment request can result in a duplicate charge.
I would use:
Consider a situation where the payment provider processes the payment but the network connection fails before your service receives the response.
The system now has an uncertain outcome.
Blindly retrying could create a second charge.
Instead, the system should query the provider or perform reconciliation before deciding whether another payment attempt is required.
I would avoid a big-bang rewrite.
Instead, identify a business capability with a clear boundary and extract it gradually.
The Strangler Fig approach can gradually move functionality from the monolith into new services.
+----------------+
Request -------->| Routing Layer |
+-------+--------+
|
+------+------+
| |
v v
New Microservice MonolithInitially, most functionality can remain in the monolith. Selected functionality is gradually routed to the new service.
Feature flags, contract testing, observability and controlled rollout can reduce migration risk.
This is a common senior-level system design scenario because it combines several microservices concepts into one workflow.
A possible architecture is:
+-----------+
| Payment |
+-----+-----+
|
|
Client → Gateway → Order → Kafka
|
+------+------+
| |
v v
Inventory NotificationA simplified business flow could be:
Order Created
|
v
Payment Confirmed
|
v
Inventory Reserved
|
v
Order Confirmed
|
v
Notification SentThe Order Service owns the order state. Payment owns payment state, and Inventory owns inventory state.
Each service performs its own local transaction.
The overall workflow can use a Saga.
The order can transition to a payment-failed state. The customer can be informed and the workflow can stop or be retried depending on the failure type.
A compensating action can refund or cancel the payment.
Payment Success
|
v
Inventory Failed
|
v
Refund Payment
|
v
Order CancelledNotification failure should generally not roll back the order. Notification can be retried independently.
Every event should have a unique event ID. Consumers should be idempotent and maintain enough state to prevent duplicate business effects.
Use bounded retries with exponential backoff and jitter. Permanent failures should not be retried forever.
Workflow state should be persisted. After a restart, the service should be able to determine which operations were completed and which remain pending.
A reconciliation process can periodically identify orders that remain in an intermediate state for too long.
For example:
ORDER_CREATED
|
| Payment never completed
|
v
Reconciliation Job
|
v
Retry / Cancel / InvestigateThis is an important production consideration because distributed workflows can fail in ways that are not immediately visible to any single service.
A senior-level answer should go beyond naming technologies.
For example, saying "I will use Kafka" is not enough.
A stronger answer explains:
The same principle applies to technologies such as Redis, Kubernetes, API Gateway and databases.
Focus on service boundaries, communication patterns, data ownership, consistency, resilience, scalability, observability, security and deployment strategy.
No. Kafka should be introduced when asynchronous communication, event distribution or decoupling provides a meaningful benefit. Simple request-response operations may be better handled synchronously.
Sharing a database creates coupling around schemas and transactions. A stronger microservice boundary generally gives each service ownership of its data.
Focusing only on the happy path. A senior-level design should explain what happens when a dependency becomes slow, a message is duplicated, a database becomes unavailable, a deployment fails or a service restarts.
No. Saga is useful when a business workflow spans multiple services and requires coordinated state changes or compensating actions. Not every interaction between services is a distributed transaction.
Strong microservices system design is not about drawing the largest architecture or listing the most technologies.
It is about explaining why each component exists and what happens when the system does not behave as expected.
For senior Java and Spring Boot interviews, be prepared to discuss the happy path as well as retries, duplicate requests, partial failures, data consistency, recovery, observability, scalability and backward compatibility.
Those trade-offs are what turn a collection of independent services into a production-ready distributed system.
0 Comments