Ch. 10

Microservices interview questions & answers

Designing and running distributed services: boundaries, API gateways, synchronous vs async communication, resilience patterns, data consistency and observability.

57 interview questions22 quiz questions29 notes
your progress0%

Notes in this chapter

Filter all notes →
read ✓Microservices · hard

Microservices Anti-Corruption Layer

Protect a service's domain model from a foreign or legacy model with a translation layer at the boundary.

~2 min readread →
read ✓Microservices · hard

Microservices API Versioning and Evolution

Evolve service APIs without breaking consumers using additive changes, explicit versioning and consumer-driven contracts.

~2 min readread →
read ✓Microservices · hard

Microservices Backend for Frontend

Use a per-client BFF to aggregate services and shape responses, without letting it become a shared god service.

~2 min readread →
read ✓Microservices · hard

Microservices Distributed Locking

Coordinate exclusive access across instances with leases and fencing, and prefer partitioning or idempotency when you can.

~2 min readread →
read ✓Microservices · hard

Microservices Event Sourcing

Store events as the source of truth, rebuild state by replay, and manage versioning with snapshots.

~2 min readread →
read ✓Microservices · mid

Microservices Health Check Endpoints

Separate liveness from readiness, keep deep dependency checks off the restart path, and avoid the restart loop.

~2 min readread →
read ✓Microservices · hard

Microservices Schema Registry

Register event schemas centrally, enforce compatibility on publish, and version them so consumers are never surprised.

~2 min readread →
read ✓Microservices · hard

Microservices Sidecar Proxies

Move cross-cutting networking concerns into a co-located proxy, configure them centrally, and watch for hidden retries.

~2 min readread →
read ✓Microservices · hard

Microservice Configuration and Safe Rollouts

Treat config changes like deploys: validate before applying, roll out by stable user buckets or by instance, and keep a fast rollback.

~7 min readread →
read ✓Microservices · easy

Microservice Consumers and Duplicate Events

Why at-least-once delivery hands a consumer the same event twice, and how a processed-message table in one transaction stops a double credit.

~6 min readread →
read ✓Microservices · mid

Microservice Deadlines Across Call Chains

Why per-hop timeouts keep services working after the client has left, and how passing a remaining-time budget down the chain stops it.

~6 min readread →
read ✓Microservices · hard

Microservice Tracing and Request Causality

How trace and span ids travel in the W3C traceparent header, why a queue hop often splits a trace in two, and how sampling shapes what you see.

~6 min readread →
read ✓Microservices · mid

Microservice Event Ordering and Partition Keys

Why one order's events arrive out of sequence without a partition key, what a key does and doesn't guarantee, and how versions catch the rest.

~6 min readread →
read ✓Microservices · mid

Microservice Retry Budgets and Amplification

How three layers of retries turn one failing request into 64 database calls, and how a 10% retry budget caps extra load during outages.

~6 min readread →
read ✓Microservices · mid

Microservice Sagas and Compensation Limits

Running an order saga across inventory and payments, ordering steps around the pivot, and what compensations can't undo: emails, fees, reads.

~6 min readread →
read ✓Microservices · mid

Microservice Discovery and Changing Endpoints

How callers find instances whose addresses keep changing (registries, TTLs, caches, DNS) and why a crashed instance still gets traffic.

~6 min readread →

57 Microservices interview questions study by subtopic

57 questions
  1. 1.What are microservices, and how do they differ from a monolithic architecture?easy

    Microservices is an architectural style where an application is built as a suite of small services. Each one runs in its own process, owns its own data, and talks to the others over lightweight mechanisms such as HTTP APIs or messaging. Each service is built around a business capability and can be deployed independently by the team that owns it.

    A monolith is a single deployable unit: all modules share one codebase, one process and usually one database, and they call each other in-process.

    The key differences:

    • Deployment: one artifact for the monolith versus independent deploys per service.
    • Scaling: scale the whole app versus scale only the busy services.
    • Data: one shared database versus a database per service.
    • Failure: in-process calls versus network calls that can fail partially.

    Microservices trade simplicity for independent deployability and team autonomy, so they pay off mainly for larger systems and organizations.

    What interviewers listen for
    • Small services built around business capabilities
    • Independently deployable; each service owns its data
    • Communicate over the network: HTTP, gRPC or messaging
    • Monolith: one deployable, one process, shared database
    • Trades simplicity for autonomy and independent scaling

    Likely follow-up: How small should a microservice be?

  2. 2.What are the main advantages and disadvantages of a microservices architecture?easy

    Advantages

    • Independent deployment: a team ships its service without coordinating a big release.
    • Team autonomy: small teams own a service end to end, which helps the organization scale.
    • Independent scaling: scale checkout without scaling the whole application.
    • Fault isolation: if you design for it, a failing non-critical service degrades one feature instead of taking everything down.
    • Technology flexibility: each service can pick the stack that suits it.

    Disadvantages

    • Distributed-systems complexity: network latency, partial failures, and timeouts and retries everywhere.
    • Data consistency: no ACID transactions across services, so you need sagas and eventual consistency.
    • Operational overhead: CI/CD, monitoring, tracing and a container platform for many deployables.
    • Harder testing and debugging: one request crosses many services and log streams.

    It's a trade: you buy autonomy and scalability, and you pay for them in operational and design complexity.

    What interviewers listen for
    • Independent deployment and team autonomy
    • Scale and isolate failures per service
    • Cost: network failures, latency, distributed debugging
    • No cross-service ACID transactions; eventual consistency
    • Needs mature CI/CD, monitoring and automation
  3. 3.When should you NOT use microservices?easy

    I'd avoid them, or at least start with a well-structured monolith, when:

    • The team is small. If one or two teams own everything, microservices add coordination and operational cost without the autonomy benefit.
    • The domain is new or unclear. Service boundaries are expensive to move later, and wrong ones give you a distributed monolith. It's easier to discover boundaries inside a monolith first.
    • Operational maturity is missing. Without automated CI/CD, containers, centralized logging, metrics and tracing, dozens of services become unmanageable.
    • Most operations need strong consistency across what would become several services, which forces sagas everywhere.
    • Scale and release cadence don't require it. A monolith scales horizontally well for many products.

    Martin Fowler calls this the "microservice premium": the extra cost is only worth paying once the system is complex enough that a monolith is hard to manage. A common path is monolith first, then extract services where there's a clear need.

    What interviewers listen for
    • Small team or early-stage product
    • Unclear domain means boundaries will likely be wrong
    • Missing CI/CD, observability and automation
    • Microservice premium: the cost only pays off with complexity
    • Prefer monolith first; extract services when needed

    Likely follow-up: What signals tell you it is time to split a monolith?

  4. 4.How do you decide the boundaries of a microservice when decomposing a system?mid

    I decompose around the business, not around technical layers.

    • By business capability: what the business does, such as order management, payments, inventory or shipping. Capabilities are stable, so services built on them are too.
    • By DDD subdomain and bounded context: a bounded context is a boundary inside which one model and its language stay consistent. "Product" in the catalog context is a different model from "product" in shipping, so they belong in different services with their own models.

    Then I sanity-check each candidate:

    • High cohesion: things that change together live together.
    • Low coupling: it can do most of its work without synchronous calls to others.
    • Ownership: one team can own it end to end.
    • Data: it owns its data, and no aggregate (a consistency boundary) spans two services.

    Anti-patterns: one service per table (entity services), splitting by layer, and services so fine-grained that every request fans out into chatty calls.

    What interviewers listen for
    • Decompose by business capability or DDD subdomain
    • Bounded context: one consistent model and language
    • High cohesion, low coupling, single-team ownership
    • Aggregates must not span services
    • Avoid entity services and layer-based splits

    Likely follow-up: What is an aggregate in DDD, and why does it matter for service boundaries?

  5. 5.What is the database-per-service pattern, and why is sharing one database between services discouraged?easy

    Each service owns its data, and other services reach it only through that service's API or events, never by querying its tables directly. Ownership can be a private set of tables, a private schema, or a separate database server; what matters is that nobody else touches it.

    Why:

    • Loose coupling: a service can change its schema without breaking others.
    • Independent deployment and scaling: no shared migrations to coordinate.
    • Polyglot persistence: one service can use PostgreSQL, another a document store or a search index.

    A shared database quietly couples services: a renamed column breaks three teams, and one service's heavy query slows everyone down.

    The costs are real:

    • Queries that join data across services need API composition or a CQRS read model.
    • Updates that span services need sagas instead of ACID transactions.
    • Some data gets duplicated and is only eventually consistent.
    What interviewers listen for
    • Service data is private; access only via API or events
    • Private tables, schema or server all qualify
    • Enables independent schema changes and polyglot persistence
    • Cross-service queries: API composition or CQRS
    • Cross-service updates: sagas, not ACID transactions

    Likely follow-up: How would you build a report that needs data from five services?

  6. 6.What is an API gateway, and what responsibilities does it usually take on?easy

    An API gateway is the single entry point that external clients call; it routes each request to the right internal service, so clients don't need to know how many services exist or where they run.

    It usually handles cross-cutting concerns:

    • Routing and load balancing to backend services.
    • Authentication: validating tokens at the edge before traffic gets in.
    • Rate limiting and quotas per client or API key.
    • TLS termination, CORS and request size limits.
    • Aggregation: combining several service calls into one response to cut client round trips.
    • Protocol translation, such as REST outside and gRPC inside.
    • Observability: access logs, metrics, and starting the distributed trace.

    The risks: it sits on every request path, so it must be highly available and fast, and it shouldn't grow into a shared monolith full of business logic that every team has to change. Keep it to routing and cross-cutting concerns; business rules belong in the services.

    What interviewers listen for
    • Single entry point that routes to internal services
    • Auth, rate limiting and TLS termination at the edge
    • Can aggregate calls and translate protocols
    • Must be highly available; it is on every path
    • Keep business logic out of the gateway

    Likely follow-up: How does an API gateway differ from a service mesh? · What is the Backend for Frontend pattern?

  7. 7.Compare synchronous and asynchronous communication between microservices. When would you use each?easy

    Synchronous (REST or gRPC request/response): the caller waits for the answer.

    • Simple to reason about, with an immediate result, which suits queries and user-facing reads.
    • But it creates temporal coupling: both services must be up at the same moment. Availability multiplies along a call chain, so three services at 99.9% each give roughly 99.7% for the chain, and latencies add up.

    Asynchronous (messages or events through a broker such as Kafka or RabbitMQ): the sender publishes and moves on.

    • Decouples services in time: a consumer can be down and catch up later, and the broker absorbs load spikes.
    • New consumers can subscribe without changing the producer.
    • But you get eventual consistency, a broker to operate, duplicate or out-of-order messages to handle, and flows that are harder to trace.

    Rule of thumb: synchronous for queries where the caller needs the data now; asynchronous for state changes that can finish in the background and for telling other services that something happened.

    What interviewers listen for
    • Sync: simple and immediate, but temporally coupled
    • Availability and latency compound across sync call chains
    • Async: decoupled in time; the broker buffers spikes
    • Async costs: eventual consistency, duplicates, harder tracing
    • Sync for queries; async for state changes and events
  8. 8.What is service discovery? Explain client-side versus server-side discovery.easy

    Instances come and go and change addresses as they scale or restart, so callers need a way to find a healthy one. Instances are recorded in a service registry, either by registering themselves or by the platform doing it for them, and they're removed when health checks fail.

    • Client-side discovery: the caller queries the registry and picks an instance itself with a client-side load balancer. Eureka with Spring Cloud LoadBalancer is the classic Java example. It saves a hop and allows smarter balancing, but every language and framework needs a discovery client.
    • Server-side discovery: the caller sends the request to a router or load balancer, which looks up the registry and forwards it. Clients stay simple. In Kubernetes, a Service gives a stable DNS name and virtual IP and the platform routes to ready pods, so most teams on Kubernetes don't run a separate registry.

    The trade-off is client complexity versus an extra hop through a component that must be highly available.

    # Callers use http://inventory-service instead of pod IPs
    apiVersion: v1
    kind: Service
    metadata:
      name: inventory-service
    spec:
      selector:
        app: inventory      # routes to ready pods with this label
      ports:
        - port: 80
          targetPort: 8080
    What interviewers listen for
    • Registry tracks healthy instances and their addresses
    • Client-side: caller queries the registry and load-balances
    • Server-side: a router or load balancer does the lookup
    • Kubernetes Services give DNS-based server-side discovery
    • Self-registration versus platform (third-party) registration
  9. 9.What is the circuit breaker pattern, and what are its states?easy

    A circuit breaker wraps calls to a remote dependency and stops calling it when it's clearly failing. The caller fails fast instead of tying up threads waiting on timeouts, and the struggling service gets room to recover.

    It has three states:

    • Closed: calls pass through while the breaker records successes, failures and slow calls in a sliding window.
    • Open: once the failure rate or slow-call rate crosses a threshold, calls are rejected immediately, usually going to a fallback. In Resilience4j a rejected call throws CallNotPermittedException.
    • Half-open: after a wait, a few trial calls are let through. If they succeed the breaker closes; if not, it opens again.

    Good practice: require a minimum number of calls before evaluating the rate, so two failures at startup don't trip it; use one breaker per dependency; and emit metrics and alerts on state changes. A circuit breaker complements timeouts and retries rather than replacing them.

    resilience4j:
      circuitbreaker:
        instances:
          paymentService:
            slidingWindowType: COUNT_BASED
            slidingWindowSize: 20
            minimumNumberOfCalls: 10
            failureRateThreshold: 50        # percent
            slowCallDurationThreshold: 2s
            slowCallRateThreshold: 50
            waitDurationInOpenState: 30s
            permittedNumberOfCallsInHalfOpenState: 3
    What interviewers listen for
    • Fails fast when a dependency is unhealthy
    • Closed, open and half-open states
    • Trips on failure-rate or slow-call-rate thresholds
    • Half-open lets trial calls through before closing
    • Pair with timeouts, fallbacks and monitoring

    Likely follow-up: Should the retry wrap the circuit breaker, or the other way around?

  10. 10.What is the saga pattern? Compare choreography and orchestration.mid

    A saga keeps data consistent across services without a distributed transaction. It's a sequence of local transactions: each service updates its own database and triggers the next step with a message or event. If a step fails, the saga runs compensating transactions that semantically undo the steps already done, such as releasing reserved stock or refunding a payment.

    Two ways to coordinate one:

    • Choreography: no coordinator. Each service reacts to events and publishes its own: orders emits OrderCreated, inventory reacts and emits StockReserved, and so on. It's loosely coupled and fine for a few steps, but the flow is implicit and spread across services, so it gets hard to follow, change and debug as it grows.
    • Orchestration: a saga orchestrator sends commands to each participant and reacts to their replies. The flow lives in one place, which is easier to understand, test and monitor, at the cost of a central component you must keep free of other services' business logic.

    I use choreography for short, simple flows and orchestration for longer ones with complex compensation.

    What interviewers listen for
    • Sequence of local transactions, no distributed lock
    • Compensating transactions undo completed steps on failure
    • Choreography: services react to events, no coordinator
    • Orchestration: a central coordinator sends commands
    • Sagas lack isolation and are eventually consistent

    Likely follow-up: How do you handle a compensating transaction that itself fails?

  11. 11.How do you handle a business transaction that spans several services? Why is two-phase commit (2PC) usually avoided?hard

    Inside one service I use a normal local ACID transaction. Across services there's no shared database, so the choice is a distributed transaction (2PC) or a saga.

    In two-phase commit, a coordinator asks every participant to prepare (do the work, hold its locks, and vote yes or no), then tells them all to commit or abort. It gives atomicity, but:

    • It blocks: a participant that voted yes must hold its locks until it hears the decision, and if the coordinator fails at that moment, it's stuck.
    • Availability drops: every participant must be up for anything to commit, so each added service lowers the odds.
    • Latency and throughput suffer from extra round trips and long-held locks.
    • Support is limited: many NoSQL stores and message brokers don't take part in XA transactions, and it couples services to each other's databases.

    So in microservices I use a saga, local transactions plus compensations, with a transactional outbox to publish events reliably, and I design for eventual consistency.

    What interviewers listen for
    • Local ACID inside a service; nothing shared across
    • 2PC: prepare and vote, then commit or abort
    • Blocking: participants hold locks if the coordinator fails
    • Lower availability and throughput; limited XA support
    • Prefer sagas plus the outbox; accept eventual consistency

    Likely follow-up: Is 2PC ever a reasonable choice?

  12. 12.What is eventual consistency, and how do you design a system and its user experience around it?mid

    Eventual consistency means that if no new updates are made, every copy of the data converges on the same value, but for a while different services or replicas can give different answers. In microservices it's the normal result of database per service plus asynchronous events: the order service knows an order is paid slightly before the shipping service's copy does.

    How I design for it:

    • Model intermediate states such as PENDING, APPROVED and REJECTED instead of pretending an operation finished instantly.
    • UX: return 202 Accepted with a status resource, show "processing", and update by polling or push when it's done.
    • Read your own writes: right after a change, read from the owning service, or return the new state in the response, not from a lagging read model.
    • Idempotent, order-tolerant consumers, so redelivery and reordering can't corrupt state.
    • Monitor lag for each read model, and reconcile periodically.

    Where the business truly needs strong consistency, such as never overselling the last item, I keep that invariant inside a single service.

    What interviewers listen for
    • Copies converge once updates stop; reads may be stale
    • Natural result of database per service plus events
    • Model pending states; 202 Accepted with a status resource
    • Read-your-writes from the owning service
    • Keep hard invariants inside one service
  13. 13.Compare Kafka and RabbitMQ. How do you choose between them?mid

    They're built on different models.

    Kafka is a distributed, partitioned, append-only log. Producers write records to a topic's partitions, records are retained for a configured time or size whether or not anyone has read them, and each consumer group tracks its own offset.

    • High throughput, horizontal scaling through partitions, and ordering within a partition.
    • Replay: a new consumer can read history from the start, which suits event streaming, CDC, event sourcing and analytics pipelines.
    • Consumers pull; per-message routing is limited.

    RabbitMQ is a message broker built around AMQP. Producers publish to exchanges (direct, topic, fanout, headers), which route messages to queues through bindings, and messages are normally removed once a consumer acknowledges them.

    • Flexible routing, per-message acknowledgements, TTLs, priorities and dead-letter exchanges.
    • A natural fit for task queues, work distribution and request/reply.
    • Recent versions add streams, an append-only log type, but replay-heavy workloads remain Kafka's home ground.

    Rule of thumb: Kafka for high-volume event streams that many consumers read and replay; RabbitMQ for task queues and rich routing.

    What interviewers listen for
    • Kafka: partitioned, retained log; consumers track offsets
    • Kafka: replay, high throughput, per-partition ordering
    • RabbitMQ: exchanges route to queues through bindings
    • RabbitMQ: messages removed on ack; flexible routing
    • Kafka for event streams; RabbitMQ for task queues
  14. 14.What is idempotency, and how would you use an idempotency key to make a payment API safe to retry?mid

    An operation is idempotent if doing it several times has the same effect as doing it once. That matters because clients retry on timeouts, and a timeout doesn't tell you whether the first attempt succeeded. GET, PUT and DELETE are idempotent by HTTP semantics; POST is not, so a retried "create payment" could charge twice.

    With an idempotency key, the client generates a unique value, such as a UUID, for each logical operation, sends it in a header (commonly Idempotency-Key) and reuses it on every retry. The server:

    • Atomically reserves the key under a unique constraint before doing any work.
    • Stores the outcome against the key and replays it for any repeat.
    • Rejects the same key sent with a different request body.
    • Returns a conflict if the first attempt is still in progress.
    • Expires keys after a retention window.

    Keys are scoped per client, and the service passes its own key on to downstream providers so the whole chain is safe to retry.

    @PostMapping("/payments")
    ResponseEntity<PaymentResult> pay(@RequestHeader("Idempotency-Key") String key, @RequestBody PaymentRequest req) {
        // INSERT under a unique constraint; returns the existing row if the key was seen
        Optional<IdempotencyRecord> seen = idempotency.tryReserve(key, req.fingerprint());
        if (seen.isPresent()) {
            IdempotencyRecord r = seen.get();
            if (!r.fingerprint().equals(req.fingerprint())) return ResponseEntity.unprocessableEntity().build();
            if (r.inProgress()) return ResponseEntity.status(HttpStatus.CONFLICT).build();
            return ResponseEntity.status(HttpStatus.CREATED).body(r.result()); // replay
        }
        PaymentResult result = payments.charge(req, key); // pass the key downstream too
        idempotency.complete(key, result);
        return ResponseEntity.status(HttpStatus.CREATED).body(result);
    }
    What interviewers listen for
    • Idempotent: repeating has the same effect as once
    • Timeouts are ambiguous, so clients must be able to retry
    • Client sends one unique key per logical operation
    • Server reserves the key atomically and replays the result
    • Reject reuse with a different body; expire old keys

    Likely follow-up: What happens if the service crashes after charging but before saving the result?

  15. 15.Explain at-most-once, at-least-once and exactly-once message delivery. Which one do you usually get in practice?easy

    These describe what a messaging system guarantees when something fails.

    • At-most-once: a message is delivered zero or one times. The consumer acknowledges it (or commits its offset) before processing, so a crash mid-processing loses it. Acceptable for things like metric samples.
    • At-least-once: a message is never lost but may arrive more than once. The consumer acknowledges only after processing, so a crash between processing and the ack causes a redelivery. This is the usual choice for business events.
    • Exactly-once: each message takes effect once. Exactly-once delivery across arbitrary systems and networks isn't achievable in general; what platforms offer is exactly-once processing inside a boundary, such as Kafka transactions for read-process-write between Kafka topics.

    In practice I design for at-least-once delivery plus idempotent consumers. That gives effectively-once results: duplicates still arrive, but processing one a second time changes nothing.

    What interviewers listen for
    • At-most-once: ack before processing; may lose messages
    • At-least-once: ack after processing; may duplicate
    • Exactly-once only within a boundary, such as Kafka transactions
    • Default design: at-least-once plus idempotent consumers
    • The result is effectively-once processing
  16. 16.What is the dual-write problem, and how does the transactional outbox pattern solve it?hard

    The dual-write problem appears when a service must update its database and publish a message, such as saving an order and emitting OrderPlaced. The two can't share one transaction: if the commit succeeds and the publish fails, other services never hear about the order; publish first and the commit may fail, announcing an order that doesn't exist.

    With a transactional outbox, the service inserts the event into an outbox table in the same local transaction as the business change, so both commit or neither does. A separate message relay then moves outbox rows to the broker, in one of two ways:

    • Polling publisher: periodically query unsent rows, publish them, mark them sent.
    • Transaction log tailing (CDC): a tool such as Debezium reads the database's commit log and publishes new outbox rows.

    The relay can crash after publishing but before recording that, so delivery is at-least-once; each event carries a unique id so consumers can deduplicate. Publishing each aggregate's events in commit order, keyed by aggregate id, preserves per-entity ordering.

    @Transactional
    public Order placeOrder(PlaceOrderCommand cmd) {
        Order order = orderRepository.save(Order.pending(cmd));
        // Same transaction: the event is committed if and only if the order is
        outboxRepository.save(new OutboxEvent(
            UUID.randomUUID(),          // event id, used by consumers to deduplicate
            "Order", order.getId(),     // aggregate type and id (becomes the message key)
            "OrderPlaced",
            json.write(new OrderPlaced(order.getId(), order.getTotal()))));
        return order;
    }
    // A relay (poller or CDC) publishes the outbox rows to the broker afterwards.
    What interviewers listen for
    • Dual write: DB commit and publish cannot be atomic
    • Insert the event into an outbox table, same transaction
    • Relay publishes rows: polling publisher or log tailing
    • At-least-once delivery, so consumers must be idempotent
    • Key events by aggregate id to keep per-entity order

    Likely follow-up: What are the trade-offs between a polling relay and a CDC relay? · How would you clean up the outbox table?

  17. 17.What does observability mean in a microservices system, and what are its three pillars?easy

    Observability is being able to understand what's happening inside the system, including failures nobody predicted, from the data it emits. With dozens of services you can't log into one box and read one log file.

    The three pillars:

    • Logs: timestamped records of discrete events, structured as JSON and shipped to a central store so you can search across services.
    • Metrics: numeric time series such as request rate, error rate, latency percentiles and saturation (CPU, queue depth). Cheap to keep and ideal for dashboards and alerts.
    • Traces: the path of a single request through every service it touches, split into timed spans, showing where latency and errors come from.

    They're far more useful when correlated: every log line carries the trace ID, and a spike on a dashboard links to example traces. OpenTelemetry is the vendor-neutral standard for producing all three. On top of that, I alert on symptoms users feel, such as error rate and latency against SLOs, rather than on every CPU blip.

    What interviewers listen for
    • Understand unexpected failures from emitted data
    • Logs: structured, centralized, searchable
    • Metrics: rate, errors, latency percentiles, saturation
    • Traces: one request across services as spans
    • Correlate by trace ID; alert on user-facing SLOs
  18. 18.How does distributed tracing work, and what role does OpenTelemetry play?mid

    A trace is one request's journey, made of spans: timed operations such as an HTTP call or a database query, each with the trace ID, its own span ID, its parent's span ID, attributes and a status. When a service calls another, it propagates the context in headers; OpenTelemetry's default is the W3C Trace Context traceparent header, carrying the trace ID, parent span ID and a sampling flag. The callee continues the same trace, and a backend such as Jaeger or Grafana Tempo assembles the spans into a timeline.

    OpenTelemetry is the vendor-neutral standard for this: APIs and SDKs for traces, metrics and logs; auto-instrumentation (for Java, an agent that instruments common frameworks and clients without code changes); the OTLP protocol; and the Collector, which receives, processes and exports telemetry to the backend you choose.

    Two practical points: for asynchronous flows, put the context in message headers too; and sample, either up front (head-based) or in the Collector after seeing whole traces (tail-based, which can keep every error and slow request).

    Span span = tracer.spanBuilder("charge-card").startSpan();
    try (Scope scope = span.makeCurrent()) {
        span.setAttribute("order.id", orderId);
        paymentGateway.charge(orderId, amount); // instrumented clients propagate the context
    } catch (Exception e) {
        span.recordException(e);
        span.setStatus(StatusCode.ERROR);
        throw e;
    } finally {
        span.end();
    }
    What interviewers listen for
    • A trace is a tree of timed spans sharing one ID
    • Context propagates in headers, such as W3C traceparent
    • OpenTelemetry: vendor-neutral APIs, SDKs, agent and Collector
    • Propagate context through message headers too
    • Head-based versus tail-based sampling
  19. 19.What is a correlation ID, and how do structured logs help you debug a request that crosses several services?easy

    A correlation ID is a unique identifier assigned to a request at the edge, usually by the API gateway, and passed along with every downstream call and message. Each service writes it into every log line, so searching one ID in the central log store shows the whole story. Today the trace ID from W3C Trace Context usually plays this role, which also links logs to traces; some systems still add a custom header such as X-Correlation-ID.

    Structured logging means emitting logs as key-value data, typically JSON, instead of free text, so fields like service, level, traceId and orderId can be filtered and aggregated reliably.

    Practical points:

    • In Java, put the IDs in the SLF4J MDC so every log statement includes them; OpenTelemetry's Java instrumentation can populate it for you.
    • Carry the ID in message headers for asynchronous flows.
    • Ship logs to a central store, and never log secrets, tokens or unnecessary personal data.
    {
      "timestamp": "2026-03-14T10:22:31.482Z",
      "level": "ERROR",
      "service": "payment-service",
      "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
      "spanId": "00f067aa0ba902b7",
      "orderId": "ord_8421",
      "message": "Card authorization timed out",
      "durationMs": 2003
    }
    What interviewers listen for
    • Unique ID created at the edge and passed downstream
    • Logged on every line; search one ID across services
    • The trace ID often serves as the correlation ID
    • Structured JSON logs with consistent field names
    • Use MDC in Java; carry IDs in message headers
  20. 20.Which resilience patterns do you use to stop one failing service from bringing down the others?easy

    Failures are normal in a distributed system, so I combine patterns that each cover a different gap:

    • Timeouts: never wait forever. Every remote call gets a deadline that fits the caller's own latency budget.
    • Retries with exponential backoff and jitter: recover from transient errors, but only for idempotent operations and with a small attempt limit.
    • Circuit breaker: stop calling a dependency that's failing, fail fast, and give it time to recover.
    • Bulkhead: cap concurrent calls, or give each dependency its own pool, so one slow dependency can't exhaust every thread.
    • Fallback: degrade gracefully with cached data, a sensible default, or by hiding a non-essential feature.
    • Rate limiting and load shedding: protect a service from more traffic than it can handle.

    Asynchronous messaging helps too, by removing the runtime dependency altogether. In Java, Resilience4j provides these as composable decorators. The goal is failure isolation: a problem in recommendations should cost you recommendations, not checkout.

    What interviewers listen for
    • Timeouts on every remote call
    • Retries with backoff and jitter, idempotent operations only
    • Circuit breakers fail fast; bulkheads isolate resources
    • Fallbacks degrade gracefully
    • Goal: contain failures to non-critical features

    Likely follow-up: In what order would you stack a retry, a circuit breaker and a timeout?

  21. 21.How should retries work between services? Why do you need exponential backoff and jitter?mid

    Retries recover from transient failures such as a dropped connection, a 503 during a deploy, or a brief timeout, but naive retries make outages worse.

    The rules I follow:

    • Retry only what's safe: idempotent operations, or ones protected by an idempotency key.
    • Retry only transient errors: timeouts, connection errors, 503, and 429 while honoring Retry-After. Never retry a 400 or a validation error.
    • Cap attempts, usually two or three, and keep the total inside the caller's own timeout.
    • Exponential backoff: wait longer after each failure (100 ms, 200 ms, 400 ms and so on) so a struggling service gets breathing room.
    • Jitter: randomize each wait. Without it, every client that failed at the same moment retries at the same moment, producing synchronized waves of load.
    • Retry at one layer: if three layers each make three attempts, the bottom service can receive 27 calls for one user request. Retry budgets and circuit breakers stop this amplification.
    // Exponential backoff with "full jitter": a random wait in [0, min(cap, base * 2^attempt)]
    static Duration backoff(int attempt) {
        long baseMs = 100, capMs = 5_000;
        long ceiling = Math.min(capMs, baseMs * (1L << Math.min(attempt, 20))); // 100, 200, 400...
        return Duration.ofMillis(ThreadLocalRandom.current().nextLong(ceiling + 1));
    }
    What interviewers listen for
    • Retry only idempotent operations and transient errors
    • Cap attempts and stay within the caller timeout
    • Exponential backoff gives the dependency room to recover
    • Jitter prevents synchronized retry storms
    • Retry at one layer to avoid amplification
  22. 22.Why are timeouts so important in microservices, and how do you choose their values?easy

    Without a timeout, a call to a slow or hung dependency blocks a thread or connection indefinitely. Under load those blocked threads pile up until the caller itself stops responding, and the failure cascades upstream. A slow dependency is often more dangerous than a dead one, because a dead one at least fails fast. Many HTTP and database clients default to no timeout or a very long one, so set them explicitly.

    How I set them:

    • Separate connect and request timeouts: connecting should fail fast; the request timeout covers the whole response.
    • Base them on measured latency, such as a little above the dependency's p99, not a round guess.
    • Respect the end-to-end budget: if the gateway gives a request 2 seconds, downstream timeouts plus retries must fit inside it. Propagating a deadline, as gRPC does, tells each hop how much time is left.
    • Treat a timeout as unknown, not failed: the operation may have succeeded, so retries need idempotency.
    HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofMillis(500))   // fail fast if we can't connect
        .build();
    
    HttpRequest request = HttpRequest.newBuilder(URI.create("http://inventory-service/api/stock/42"))
        .timeout(Duration.ofSeconds(2))           // deadline for the response
        .GET()
        .build();
    
    // Throws HttpTimeoutException if no response arrives within 2 seconds
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    What interviewers listen for
    • Hung calls exhaust threads and cascade upstream
    • A slow dependency is worse than a dead one
    • Set connect and request timeouts explicitly
    • Fit timeouts and retries inside the end-to-end budget
    • A timeout means an unknown outcome, not a failure
  23. 23.What is a modular monolith, and why might you choose it over microservices?mid

    A modular monolith is a single deployable application split internally into well-defined modules aligned with business capabilities, such as orders, billing and catalog. Each module exposes a public API and hides its internals, owns its own tables (ideally its own schema), and other modules can't reach into its data or internal classes.

    Why choose it:

    • Simpler operations: one build, one deploy, one process to monitor.
    • In-process calls: no network latency or partial failures between modules.
    • ACID transactions are still available when you truly need them.
    • Cheap refactoring: moving a boundary is a code change, not a cross-team migration.
    • A path forward: clean modules that own their data can be extracted into services later, when there's a real reason such as independent scaling or team autonomy.

    The catch is discipline. Boundaries must be enforced or the codebase erodes into a big ball of mud. In Java, ArchUnit tests, the Java module system or Spring Modulith can enforce the rules in the build.

    What interviewers listen for
    • One deployable with strictly separated business modules
    • Modules expose APIs and own their data
    • Simple ops, in-process calls, ACID transactions
    • Modules can be extracted into services later
    • Enforce boundaries in the build, such as ArchUnit
  24. 24.What is the Backend for Frontend (BFF) pattern, and how does it differ from a general-purpose API gateway?mid

    A BFF is a backend built for one specific client experience, such as the web app, the mobile app or a partner API, instead of one gateway serving everyone. Each BFF shapes the API for its client: it aggregates calls to several downstream services, trims payloads to what each screen needs, and adapts to the client's constraints, such as smaller responses for mobile.

    Compared with a single general-purpose gateway:

    • A shared gateway tends to accumulate conflicting client-specific logic and becomes a bottleneck that every team has to change.
    • A BFF is usually owned by the frontend team that uses it, so they can evolve it at their own pace.

    The trade-offs are some duplicated logic across BFFs and more services to run. I keep BFFs thin, doing aggregation and presentation shaping only, with business rules staying in the domain services. Many setups use both: an edge gateway for cross-cutting concerns such as TLS, authentication and rate limiting, with BFFs behind it.

    What interviewers listen for
    • One backend per client type or experience
    • Aggregates and shapes data for that client
    • Owned by the frontend team that consumes it
    • Avoids one bloated gateway, at the cost of duplication
    • Business logic stays in domain services
  25. 25.Compare REST and gRPC for service-to-service communication. When would you use each?mid

    REST usually means resource-oriented HTTP APIs with JSON.

    • Universal: every language, browser, proxy and tool understands it, and it's easy to debug with curl.
    • HTTP caching, simple versioning, and a natural fit for public APIs.
    • Costs: text payloads are larger and slower to parse, and contracts stay informal unless you maintain OpenAPI specs.

    gRPC is an RPC framework over HTTP/2 that uses Protocol Buffers.

    • Contract-first: .proto files generate typed clients and servers in many languages.
    • A compact binary encoding and multiplexed connections give lower latency and higher throughput.
    • Streaming (client, server and bidirectional) and built-in deadlines.
    • Costs: browsers need gRPC-Web or a proxy, payloads aren't human-readable, and because HTTP/2 connections are long-lived and multiplexed, you need request-level (L7) load balancing; connection-level balancing can pin all traffic to one instance.

    My default: REST for public and browser-facing APIs, gRPC for high-volume internal calls where performance and strict contracts matter.

    What interviewers listen for
    • REST: JSON over HTTP, universal and easy to debug
    • gRPC: HTTP/2 plus Protocol Buffers, generated clients
    • gRPC adds streaming, deadlines and lower latency
    • gRPC needs L7 load balancing; gRPC-Web for browsers
    • REST for public APIs; gRPC for heavy internal traffic
  26. 26.How do you handle authentication and authorization across microservices?mid

    I authenticate users once, at the edge, and let every service authorize locally.

    • Identity provider: users sign in through an OAuth 2.0 / OpenID Connect provider, which issues a short-lived access token, usually a signed JWT.
    • Gateway: validates the token on every request, rejecting bad ones early, and forwards it downstream.
    • Each service validates too: it checks the signature against the provider's public keys (JWKS) plus the iss, aud and exp claims, locally, with no call to the auth server per request. Trusting the gateway alone assumes nobody can ever get inside the network.
    • Authorization lives in the service that owns the data: coarse checks from scopes or roles in the token, fine-grained rules such as "can this user see this order" in business logic.
    • Service identity: for calls a service makes on its own behalf, use the OAuth 2.0 client credentials grant or mTLS.

    JWTs are hard to revoke before they expire, so keep them short-lived and use refresh tokens.

    # Spring Boot resource server: validates JWTs locally with the issuer's published keys
    spring:
      security:
        oauth2:
          resourceserver:
            jwt:
              issuer-uri: https://auth.example.com/realms/shop
    What interviewers listen for
    • OAuth 2.0 / OIDC provider issues short-lived JWTs
    • Gateway validates at the edge and forwards the token
    • Services verify signature and claims locally via JWKS
    • Fine-grained authorization in the owning service
    • Client credentials or mTLS for service identity

    Likely follow-up: How would you pass user identity along with an asynchronous event?

  27. 27.What is CQRS, and when is it worth the extra complexity?mid

    CQRS, Command Query Responsibility Segregation, separates the model that handles writes (commands) from the model that serves reads (queries). The write side enforces business rules on a normalized model; the read side keeps one or more denormalized views shaped for specific queries, often in a different store such as a search index or a document database. In microservices the read side is usually updated from events, so it's eventually consistent.

    Where it helps:

    • Read and write loads or shapes differ a lot, such as a product page read constantly but updated rarely.
    • Queries need data from several services: a view subscribes to their events and pre-joins the data, instead of calling every service on each request.
    • Different read patterns, such as full-text search, dashboards and feeds, each want their own view.

    Where it doesn't: simple CRUD. It adds a second model, synchronization code, replication lag users can notice, and more to operate, so I apply it to the parts of a system that need it rather than as a default architecture.

    What interviewers listen for
    • Separate write model (commands) from read model (queries)
    • Read side: denormalized views, often in another store
    • Views updated from events, so eventually consistent
    • Good for cross-service queries and skewed read loads
    • Overkill for simple CRUD; apply it selectively
  28. 28.What is event sourcing? What are its benefits and its pitfalls?hard

    With event sourcing, a service stores the sequence of events that happened to an entity instead of its current state. An account isn't a row with a balance; it's AccountOpened, MoneyDeposited, MoneyWithdrawn and so on, and current state is rebuilt by replaying them in order. The store is append-only: to change state you append an event, checking the expected version for optimistic concurrency.

    Benefits:

    • A complete audit trail and the ability to reconstruct state at any point in time.
    • The stored events can feed other services, with no dual write.
    • New projections (read models) can be built later by replaying history.

    Pitfalls:

    • Querying current state needs projections, so it usually comes with CQRS and eventual consistency.
    • Schema evolution: old events live forever, so you need versioning or upcasting.
    • Long streams need snapshots to rebuild quickly.
    • Erasing personal data is hard; crypto-shredding is a common answer.
    • A steep learning curve for the team.

    I'd use it where history is intrinsically valuable, such as ledgers or audit-heavy domains, not as a default.

    [
      { "streamId": "account-42", "version": 1, "type": "AccountOpened",  "data": { "owner": "cus_19" } },
      { "streamId": "account-42", "version": 2, "type": "MoneyDeposited", "data": { "amount": 100 } },
      { "streamId": "account-42", "version": 3, "type": "MoneyWithdrawn", "data": { "amount": 30 } }
    ]
    What interviewers listen for
    • Store events, not current state; append-only
    • Current state is rebuilt by replaying events
    • Audit trail, time travel, new projections from history
    • Needs projections, snapshots and event versioning
    • Use where history matters, not by default
  29. 29.How would you migrate a monolith to microservices? Explain the strangler fig pattern.mid

    I'd avoid a big-bang rewrite, which delays value for months and often fails. The strangler fig pattern, named by Martin Fowler after a vine that gradually grows around a tree and replaces it, migrates incrementally:

    • Put a facade in front of the monolith, such as an API gateway or reverse proxy, so traffic can be rerouted without clients noticing.
    • Pick a first slice: a well-bounded capability with clear value and manageable risk, often one that changes frequently or needs to scale independently.
    • Build it as a service with its own data and route its requests there. An anti-corruption layer keeps the monolith's model from leaking into the new one.
    • Migrate the data: keep it in sync during the transition, for example with CDC or events, until the new service is the source of truth.
    • Repeat, deleting the dead code from the monolith as you go.

    Each step is small and reversible: if the new service misbehaves, route traffic back. Adding observability and tests around the monolith first makes the whole process much safer.

    What interviewers listen for
    • Incremental migration, not a big-bang rewrite
    • A facade or proxy routes traffic between old and new
    • Start with a well-bounded, valuable slice
    • Anti-corruption layer and data sync during transition
    • Each step is reversible; delete old code as you go
  30. 30.What is the difference between liveness, readiness and startup probes, and what should each one check?easy

    They answer different questions, and Kubernetes reacts to each one differently.

    • Liveness: is the process healthy, or stuck and in need of a restart? If it keeps failing, the kubelet restarts the container. Check only the process itself, such as a deadlock or unrecoverable state, never downstream dependencies: if the database blips and every pod's liveness check fails, Kubernetes restarts them all and a small outage becomes a big one.
    • Readiness: can this instance take traffic right now? If it fails, the pod is removed from the Service's endpoints but not restarted. Useful during warm-up, graceful shutdown or overload. Be careful with shared dependencies here too, since that can pull every instance out at once.
    • Startup: has a slow-starting app finished booting? Liveness and readiness checks are held off until it succeeds, so a long startup isn't mistaken for a hang.

    Spring Boot Actuator exposes /actuator/health/liveness and /actuator/health/readiness for exactly this.

    startupProbe:
      httpGet: { path: /actuator/health/liveness, port: 8080 }
      periodSeconds: 5
      failureThreshold: 30     # allow up to ~150 s to start
    livenessProbe:
      httpGet: { path: /actuator/health/liveness, port: 8080 }
      periodSeconds: 10
      failureThreshold: 3      # restart after ~30 s of failures
    readinessProbe:
      httpGet: { path: /actuator/health/readiness, port: 8080 }
      periodSeconds: 5
      failureThreshold: 2      # stop routing traffic quickly
    What interviewers listen for
    • Liveness failure restarts the container
    • Readiness failure removes the pod from Service endpoints
    • Startup probe holds off the other probes during boot
    • Never check downstream dependencies in liveness
    • Actuator exposes liveness and readiness health groups
  31. 31.How do you manage configuration and secrets across many microservices and environments?easy

    Following the Twelve-Factor App, anything that varies between environments (URLs, credentials, feature flags, pool sizes) lives outside the code, and the same build artifact is promoted from test to production with different config.

    Common approaches:

    • Platform config: Kubernetes ConfigMaps injected as environment variables or mounted files.
    • A config server: Spring Cloud Config serves properties from a Git repository, so changes are versioned, reviewed and auditable; Consul's key-value store is another option.
    • Secrets in a dedicated store: Vault or a cloud secret manager, with access control, auditing and rotation. Kubernetes Secrets are only base64-encoded by default, so enable encryption at rest and restrict access.

    Good practices:

    • Validate config at startup and fail fast if something is missing.
    • Be deliberate about dynamic refresh (for example Spring's @RefreshScope): handy for feature flags, risky for things like pool sizes.
    • Never commit secrets to Git or bake them into images.
    # order-service: pull shared, per-environment config from Spring Cloud Config
    spring:
      application:
        name: order-service     # the config server looks up files by this name
      config:
        import: "optional:configserver:http://config-server:8888"
    What interviewers listen for
    • Config lives outside code; one artifact for all environments
    • ConfigMaps, env vars, or a Git-backed config server
    • Secrets in Vault or a cloud secret manager
    • Kubernetes Secrets need encryption at rest and RBAC
    • Validate at startup; be careful with hot refresh
  32. 32.What is the Twelve-Factor App, and which of its factors matter most for microservices?easy

    The Twelve-Factor App is a methodology for building software-as-a-service apps that are portable, easy to deploy and scale cleanly on cloud platforms. The factors are: codebase, dependencies, config, backing services, build/release/run, processes, port binding, concurrency, disposability, dev/prod parity, logs and admin processes.

    The ones I lean on most for microservices:

    • Config: store config in the environment, not the code, so one build runs everywhere.
    • Backing services: treat databases, queues and caches as attached resources, swappable by changing config.
    • Build, release, run: strictly separate stages; a release is an immutable build plus config.
    • Processes: stateless processes with state kept in backing services, so any instance can serve any request and you can scale out.
    • Disposability: fast startup and graceful shutdown on SIGTERM, so the platform can move instances freely.
    • Logs: write logs as an event stream to stdout and let the platform collect them.
    • Dev/prod parity: keep environments as similar as possible.
    What interviewers listen for
    • Methodology for portable, cloud-ready services
    • Config in the environment; one build everywhere
    • Stateless processes; state in backing services
    • Disposability: fast startup and graceful shutdown
    • Logs as an event stream to stdout
  33. 33.Why are containers and Kubernetes such a common foundation for microservices?easy

    Containers package a service with its runtime and dependencies into an immutable image. The same image runs on a laptop, in CI and in production, which removes "works on my machine" problems and lets each service choose its own stack. They're light enough to run many per host.

    Running dozens of services by hand doesn't scale, and Kubernetes automates it:

    • Scheduling: places containers on nodes based on the resources they request.
    • Self-healing: restarts failed containers and replaces pods from failed nodes, guided by health probes.
    • Service discovery and load balancing: Service objects give stable DNS names.
    • Scaling: the Horizontal Pod Autoscaler adds or removes replicas based on metrics such as CPU.
    • Rollouts: rolling updates and rollbacks through Deployments.
    • Config and secrets: ConfigMaps and Secrets injected at runtime.

    It's declarative: you describe the desired state and controllers keep converging on it. The cost is real complexity, so small teams often choose a managed Kubernetes service or a simpler platform.

    What interviewers listen for
    • Images package code and dependencies immutably
    • The same image runs from laptop to production
    • Kubernetes schedules, self-heals and scales containers
    • Built-in discovery, rolling updates, config and secrets
    • Declarative desired state, but significant complexity
  34. 34.Compare rolling, blue-green and canary deployments. How do you release a microservice safely?mid
    • Rolling update: replace instances a few at a time; Kubernetes Deployments do this by default. No extra capacity needed, but old and new versions serve traffic side by side during the rollout, and rolling back is another rollout.
    • Blue-green: run the new version (green) next to the current one (blue), test it, then switch all traffic at once at the router or load balancer. Rollback is an instant switch back, but you need double capacity for a while.
    • Canary: send a small share of real traffic, say a few percent, to the new version, compare its error rate and latency with the old one, and increase the share step by step while it stays healthy. It limits the blast radius and catches problems tests miss, but needs good metrics and traffic splitting, often via a service mesh.

    Whatever the strategy, versions coexist, so database changes must be backward compatible: expand and contract (add the new column, deploy, backfill, remove the old one later). Feature flags also decouple deploying code from releasing a feature.

    What interviewers listen for
    • Rolling: gradual replacement; versions coexist
    • Blue-green: instant switch and rollback, double capacity
    • Canary: small traffic share, promote on healthy metrics
    • Expand-and-contract for backward-compatible schema changes
    • Feature flags separate deploy from release
  35. 35.How do you evolve a service API without breaking its consumers? How do you approach versioning?mid

    The first goal is to avoid needing a new version at all, by making backward-compatible changes:

    • Adding optional fields, new endpoints or new optional parameters is safe.
    • Removing or renaming fields, changing a type or meaning, or making an optional field required is breaking.
    • Consumers should be tolerant readers: ignore unknown fields and depend only on what they use.

    When a breaking change is unavoidable, version explicitly:

    • URI versioning (/v2/orders) is simple and visible, and the most common choice.
    • Header or media-type versioning keeps URIs stable but is less visible and harder to try in a browser.

    Then run the old and new versions side by side, announce a deprecation timeline, track who still calls the old one, and remove it once its traffic reaches zero.

    The same rules apply to events: evolve schemas compatibly, ideally checked automatically by a schema registry. With Protobuf, never reuse a field number; mark removed ones reserved. Contract tests catch accidental breaking changes before deployment.

    What interviewers listen for
    • Prefer additive, backward-compatible changes
    • Consumers act as tolerant readers
    • URI versioning for unavoidable breaking changes
    • Run versions side by side; deprecate on a timeline
    • Same discipline for events; never reuse Protobuf field numbers
  36. 36.What are consumer-driven contract tests, and what problem do they solve?mid

    The problem: a provider changes its API, its own tests pass, and a consumer breaks in production. End-to-end tests could catch it, but they're slow, flaky and need every service deployed together.

    With consumer-driven contracts, each consumer records exactly what it needs from a provider: the requests it sends and the parts of the responses it relies on.

    • Consumer side: tests run against a mock provider generated from the contract, and the contract is published, often to a broker.
    • Provider side: the provider's build replays every consumer's contract against the real service and fails if any expectation breaks.

    So the provider learns about a breaking change in its own CI, before deploying, and knows which consumers are affected. It also reveals fields nobody uses, which makes them safe to remove.

    Pact is the best-known tool; its broker's can-i-deploy check answers whether a version is safe to release. Spring Cloud Contract is a common JVM alternative. Contract tests check the shape of interactions, not business behavior, so they complement functional tests.

    What interviewers listen for
    • Consumers declare the requests and fields they rely on
    • Consumer tests run against mocks built from the contract
    • Provider CI verifies every consumer contract
    • Catches breaking changes before deploy, without E2E tests
    • Pact or Spring Cloud Contract; checks shape, not logic
  37. 37.What testing strategy would you use for a system made of many microservices?mid

    I test each service thoroughly in isolation and keep cross-service end-to-end tests to a minimum.

    • Unit tests: domain logic, fast and plentiful.
    • Integration tests: the service against real infrastructure such as its database and broker, often started with Testcontainers, covering repositories, message handlers and serialization.
    • Component tests: the whole service running with its external dependencies stubbed, exercised through its API.
    • Contract tests: consumer-driven contracts check that services agree on APIs and message formats without deploying them together.
    • End-to-end tests: only a handful of critical journeys such as sign-up and checkout, because they're slow, flaky and expensive to maintain.

    Because staging never matches production exactly, I add testing in production safeguards: canary releases with automated rollback, feature flags, synthetic monitoring of key flows and strong observability. Resilience can be tested too, by injecting failures such as added latency or a killed instance and checking that timeouts and fallbacks behave.

    What interviewers listen for
    • Many unit tests; integration tests with Testcontainers
    • Component tests: one service with stubbed dependencies
    • Contract tests replace most cross-service E2E tests
    • Few end-to-end tests, for critical journeys only
    • Canaries, synthetic monitoring and fault injection
  38. 38.What is rate limiting, which algorithms are common, and how do you enforce a limit across many instances?mid

    Rate limiting caps how many requests a client can make in a period. It protects services from overload, abusive clients and runaway retry loops, and enforces fair use or paid quotas. Rejected requests get 429 Too Many Requests, ideally with Retry-After.

    Common algorithms:

    • Token bucket: tokens refill at a steady rate up to a capacity and each request spends one, allowing short bursts while enforcing an average rate. The most common choice.
    • Leaky bucket: requests drain at a fixed rate, smoothing bursts into a steady flow.
    • Fixed window counter: simple, but allows up to double the limit around a window boundary.
    • Sliding window (log or counter): fixes the boundary problem at some extra cost.

    Per-instance limits are simple but inexact, since the effective limit grows with the replica count. For a global limit, keep counters in a shared store such as Redis, updated atomically. The API gateway is the natural place for per-client limits. Throttling usually means the softer version: slowing or queuing requests instead of rejecting them.

    class TokenBucket {
        private final long capacity; private final double refillPerNano;
        private double tokens; private long last = System.nanoTime();
        TokenBucket(long capacity, double perSecond) {
            this.capacity = capacity; this.tokens = capacity; this.refillPerNano = perSecond / 1e9;
        }
        synchronized boolean tryAcquire() {
            long now = System.nanoTime();
            tokens = Math.min(capacity, tokens + (now - last) * refillPerNano); // refill
            last = now;
            if (tokens < 1) return false;   // caller responds with 429
            tokens -= 1; return true;
        }
    }
    What interviewers listen for
    • Caps requests per client per period; returns 429
    • Token bucket allows bursts around a steady average
    • Fixed windows allow bursts at window boundaries
    • Global limits need a shared atomic store such as Redis
    • Enforce per-client limits at the gateway
  39. 39.Which caching strategies do you use in a microservices system, and how do you stop caches from serving wrong data?mid

    Caching cuts latency and load, but every cache is a copy that can go stale.

    Patterns:

    • Cache-aside: check the cache; on a miss, read the database and fill the cache. The most common approach.
    • Read-through / write-through: the cache layer itself loads from or writes to the database, keeping both in step on writes.
    • Write-behind: writes hit the cache and are flushed to the database asynchronously. Fast, but data can be lost if the cache fails.

    Where caches live: in-process (such as Caffeine) is fastest, but each instance has its own copy; distributed (such as Redis) is shared; HTTP caching with Cache-Control and ETag works at the gateway or CDN.

    Keeping it correct:

    • A TTL on everything, sized to how stale the data may be.
    • Invalidate on change by consuming the owning service's events.
    • Prevent stampedes on hot keys with request coalescing or early refresh, and add jitter to TTLs.
    • Never cache data that must be exact, such as a balance behind a payment decision.
    What interviewers listen for
    • Cache-aside is the default pattern
    • In-process caches are fastest; distributed caches are shared
    • TTL on everything, sized to acceptable staleness
    • Invalidate using change events from the owning service
    • Prevent stampedes; never cache must-be-exact data
  40. 40.What is the bulkhead pattern, and how would you apply it in a Java service?mid

    Named after the watertight compartments in a ship's hull, a bulkhead isolates resources so that a failure in one part can't sink the whole service.

    The classic problem: a service calls three dependencies from one shared thread pool. One dependency becomes slow, every thread ends up waiting on it, and requests that never touch that dependency start failing too.

    Ways to apply it:

    • Semaphore bulkhead: cap concurrent calls to each dependency; extra calls are rejected, or wait briefly, instead of piling up.
    • Thread-pool bulkhead: give each dependency its own small pool and queue, so its slowness only exhausts its own pool.
    • Separate connection pools per downstream service or database.
    • At deployment level: separate instances or node pools for critical and non-critical workloads, or for different tenants.

    Resilience4j provides both a semaphore Bulkhead and a ThreadPoolBulkhead. Combined with timeouts and a circuit breaker, it keeps a slow recommendations service from starving checkout.

    resilience4j:
      bulkhead:                      # semaphore: limits concurrent calls
        instances:
          recommendations:
            maxConcurrentCalls: 10
            maxWaitDuration: 10ms    # then reject instead of queuing
      thread-pool-bulkhead:          # a separate pool and queue per dependency
        instances:
          reporting:
            coreThreadPoolSize: 2
            maxThreadPoolSize: 4
            queueCapacity: 20
    What interviewers listen for
    • Isolate resources so one failure cannot sink everything
    • A slow dependency can exhaust a shared thread pool
    • Semaphore limits or dedicated pools per dependency
    • Separate connection pools, instances or node pools
    • Combine with timeouts and circuit breakers
  41. 41.What is the difference between a queue and a topic, that is, point-to-point versus publish-subscribe messaging?easy
    • Queue (point-to-point): each message is delivered to one consumer. Several consumers can read from the same queue as competing consumers, which spreads work across instances. It suits commands and jobs, like "send this email" or "resize this image".
    • Topic (publish-subscribe): each message is delivered to every subscriber. The publisher doesn't know who's listening, and new subscribers can be added without changing it. It suits events: "order placed" may interest billing, shipping and analytics.

    Brokers implement these differently:

    • In RabbitMQ you publish to an exchange. One queue with several consumers gives work distribution; a fanout or topic exchange bound to one queue per subscribing service gives pub-sub.
    • In Kafka, a topic supports both at once through consumer groups: within a group each partition is read by one consumer, so the group shares the work like a queue, while each separate group receives every message, like pub-sub.

    A common setup is pub-sub between services and competing consumers among the instances of each service.

    What interviewers listen for
    • Queue: each message goes to one consumer
    • Competing consumers spread work across instances
    • Topic: every subscriber receives each message
    • Kafka consumer groups provide both semantics
    • Commands suit queues; events suit topics
  42. 42.How do Kafka partitions and consumer groups work, and how do you guarantee the ordering of events?hard

    A Kafka topic is split into partitions, each an ordered, append-only log replicated across brokers. Kafka guarantees order only within a partition, not across a whole topic.

    • Producers pick the partition, normally by hashing the record key. Every record with the same key, such as an order id, lands in the same partition and stays in order, as long as the partition count doesn't change.
    • Consumer groups: each partition is assigned to exactly one consumer in a group, so a group's parallelism is capped by the partition count and extra consumers sit idle. When members join or leave, the group rebalances.

    To keep per-entity order:

    • Key every event by the entity id.
    • Stop producer retries from reordering: the idempotent producer, the default in recent clients, preserves order even with several requests in flight.
    • Process each partition sequentially; handing records to a thread pool undoes the ordering.

    Watch for hot partitions from skewed keys, and avoid adding partitions to a topic that relies on key ordering, because keys get remapped.

    // Same key -> same partition -> events for one order are consumed in order
    producer.send(new ProducerRecord<>("order-events", order.getId(), eventJson));
    What interviewers listen for
    • Ordering is guaranteed only within a partition
    • The same key always hashes to the same partition
    • One consumer per partition within a group
    • Partition count caps consumer-group parallelism
    • Adding partitions remaps keys; beware hot partitions
  43. 43.What is event-driven architecture? How does an event differ from a command, and how much data should an event carry?mid

    In an event-driven architecture, services communicate by publishing events, facts about something that already happened, such as OrderPlaced or PaymentFailed, and other services react. The publisher doesn't know or care who's listening.

    • An event is past tense and broadcast: "this happened". Consumers decide what to do, and there can be many.
    • A command is imperative and addressed to one recipient: "reserve this stock". The sender expects it to be handled, and often expects a reply.

    How much data to include is a real design choice:

    • Event notification: a thin event with ids only, and consumers call back for details. Small, but it brings back runtime coupling and extra load.
    • Event-carried state transfer: the event carries the relevant state, so consumers can keep a local copy and keep working when the publisher is down, at the price of larger events and duplicated data.

    The benefits are loose coupling and easy extension; the costs are eventual consistency, flows that are hard to see end to end, and event schemas that become public contracts.

    {
      "eventId": "e7a1c0de-5b2f-4c1e-9d3a-2f6b8c4e1a90",
      "type": "OrderPlaced",
      "version": 2,
      "occurredAt": "2026-03-14T10:22:31Z",
      "orderId": "ord_8421",
      "customerId": "cus_19",
      "items": [{ "sku": "BOOK-17", "quantity": 2 }],
      "total": { "amount": 4998, "currency": "EUR" }
    }
    What interviewers listen for
    • Services publish facts; others react independently
    • Events are past tense and broadcast; commands are targeted
    • Event notification: thin, but consumers call back
    • Event-carried state transfer: self-contained, duplicated data
    • Event schemas are public contracts
  44. 44.How do you make a message consumer idempotent when the broker delivers messages at least once?mid

    Duplicates will happen eventually, from producer retries, relay crashes, or a consumer that processed a message but crashed before acknowledging it. So each handler must produce the same result whether a message arrives once or five times.

    Approaches:

    • Track processed message ids: every message carries a unique id. In the same local transaction as the business change, insert that id into a processed_messages table with a primary key. A duplicate either finds the id and is skipped, or fails the insert and rolls back. Checking and changing in separate transactions leaves a gap.
    • Naturally idempotent operations: "set status to PAID" is safe to apply twice; "add 10 to the balance" isn't.
    • Conditional updates: apply a change only from the expected state or version, such as UPDATE ... WHERE status = 'PENDING', which also guards against out-of-order events.
    • Upserts keyed by a business id for projections and read models.

    The processed-id table needs a retention policy, longer than any realistic redelivery window.

    @Transactional
    public void on(PaymentCompleted event) {
        // message_id is the primary key: a concurrent duplicate fails and rolls back
        if (processedMessages.existsById(event.eventId())) {
            return;                                   // already handled: just acknowledge
        }
        processedMessages.save(new ProcessedMessage(event.eventId()));
        orderRepository.markPaid(event.orderId());    // same transaction as the dedupe record
    }
    What interviewers listen for
    • Duplicates are inevitable with at-least-once delivery
    • Record processed message ids in the same transaction
    • A unique constraint catches concurrent duplicates
    • Prefer naturally idempotent or conditional updates
    • Give the dedupe table a retention policy
  45. 45.What is a dead-letter queue, and how do you handle messages that keep failing?mid

    A poison message fails every time it's processed, because of bad data, a schema mismatch or a bug. Retried forever, it blocks everything behind it (in Kafka, its whole partition) or burns resources in an endless redelivery loop.

    The fix is to separate transient failures from permanent ones:

    • Retry transient errors, such as a database timeout, a few times with backoff.
    • Move permanent failures, and messages that exhaust their retries, to a dead-letter queue (DLQ) with metadata: the error, the original topic or queue, and the attempt count.
    • Alert on DLQ depth, investigate, fix the cause, then replay the messages or discard them deliberately.

    Broker support differs. RabbitMQ has dead-letter exchanges: a message is dead-lettered when it's rejected without requeue, expires, or overflows a queue's length limit. Kafka has no built-in DLQ; frameworks add one, such as Spring Kafka's DeadLetterPublishingRecoverer and its non-blocking retry topics.

    One trade-off: moving a message aside breaks per-key ordering, so later events for that entity may need to be parked too.

    What interviewers listen for
    • Poison messages block partitions or loop forever
    • Retry transient errors with backoff, then dead-letter
    • Keep error metadata with the dead-lettered message
    • Alert on DLQ depth; fix the cause, then replay
    • Dead-lettering breaks per-key ordering
  46. 46.How do you secure service-to-service communication? Explain mTLS and the zero-trust model.hard

    Zero trust means no request is trusted just because it comes from inside the network. The perimeter model assumed everything behind the firewall was safe, so one compromised service could reach everything. Instead, every call is authenticated, authorized and encrypted, with least-privilege access.

    The building blocks:

    • mTLS (mutual TLS): both sides present certificates, so traffic is encrypted and each service cryptographically proves its identity, not just the server. Issuing and rotating certificates by hand for many services is painful, so a service mesh or a SPIFFE-based identity system usually automates it with short-lived certificates.
    • Authorization policies: once identities are known, allow only the calls that should happen, such as "only the order service may call POST /payments".
    • Tokens for user context: mTLS proves which service is calling; a JWT says which end user it's for and what they may do. Services forward the user's token or exchange it for a narrower one, and use OAuth 2.0 client credentials for their own calls.
    • Network policies add defense in depth by limiting which pods can talk at all.
    # Istio: require mTLS for every workload in the namespace
    apiVersion: security.istio.io/v1
    kind: PeerAuthentication
    metadata:
      name: default
      namespace: shop
    spec:
      mtls:
        mode: STRICT
    What interviewers listen for
    • Zero trust: network location grants no trust
    • mTLS encrypts traffic and authenticates both sides
    • Automate short-lived certificates with a mesh or SPIFFE
    • Authorization policies allow only expected calls
    • JWT carries user identity; mTLS carries service identity
  47. 47.What is a service mesh, what does it give you, and what does it cost?hard

    A service mesh moves service-to-service networking out of application code and into infrastructure. In the classic design every pod gets a sidecar proxy, commonly Envoy, that intercepts its inbound and outbound traffic, and a control plane, as in Istio or Linkerd, configures all the proxies. Some meshes also offer sidecar-less modes with shared per-node proxies.

    What it gives you, consistently across languages:

    • Security: automatic mTLS with certificate rotation, and service identities for authorization policies.
    • Traffic management: retries, timeouts, circuit breaking, and weighted routing for canaries.
    • Observability: request metrics for every hop and spans from the proxies. The application must still forward trace headers, because a proxy can't link an incoming request to the outgoing calls it causes.

    The costs:

    • Operational complexity: another distributed system to install, upgrade and debug.
    • Extra latency per hop, plus CPU and memory for the proxies.
    • Retries configured in both the mesh and the app can multiply load.

    It pays off with many services, several languages or strict mTLS needs; for a handful of services, libraries may be enough.

    What interviewers listen for
    • Sidecar proxies form the data plane; a control plane configures them
    • Automatic mTLS and identity-based authorization
    • Retries, timeouts and traffic splitting without code changes
    • Apps must still propagate trace headers
    • Costs: complexity, latency and resource overhead
  48. 48.Explain the CAP theorem and PACELC. How do they influence the design of microservices?hard

    The CAP theorem says that when a network partition occurs, a distributed data system must choose between consistency (every read sees the latest write; in CAP this means linearizability, not the C in ACID) and availability (every request to a non-failed node gets a response). Partitions can't be ruled out, so the real choice is CP or AP during a partition. It's often misquoted as "pick any two of three".

    PACELC extends it: if there's a Partition (P), choose Availability (A) or Consistency (C); Else (E), in normal operation, choose between Latency (L) and Consistency (C). Even without failures, replicating every write synchronously before answering costs latency.

    For microservices:

    • Most cross-service data is naturally AP: services keep replicated read models and accept eventual consistency, so one unreachable service doesn't stop the others.
    • Invariants that must always hold, such as a balance never going negative, belong inside one service with a strongly consistent store.
    • The choice is made per operation and per piece of data, not once for the whole system.
    What interviewers listen for
    • During a partition, choose consistency or availability
    • CAP consistency means linearizability, not ACID consistency
    • PACELC: otherwise, trade latency against consistency
    • Cross-service data is usually AP and eventually consistent
    • Keep hard invariants in one strongly consistent service
  49. 49.With a database per service, how do you implement a query that needs data owned by several services?hard

    There are two main patterns, plus one rule: never reach into another service's database.

    • API composition: a composer, such as the gateway, a BFF or the service that owns the query, calls each owning service and joins the results in memory. Simple and always current, but latency is set by the slowest call, availability drops with each dependency, and large cross-service joins or filters, like "orders over 100 euros from premium customers", get expensive.
    • CQRS read model: a service subscribes to the owning services' events and maintains its own denormalized view, pre-joined for that query and stored wherever suits it: a relational table, a document store or a search index. Queries are fast and don't depend on the other services being up, but the view is eventually consistent and needs owning: handlers, replays, lag monitoring.

    A lighter variant is data replication: keep a local copy of just the fields you need from another service's events, such as the customer's name on an order.

    I use composition for simple lookups and read models for heavy or complex queries.

    What interviewers listen for
    • Never query another service database directly
    • API composition: call the owners and join in memory
    • Composition inherits the latency and failures of every call
    • CQRS view: event-fed, denormalized, eventually consistent
    • Replicate only the fields you need locally
  50. 50.What is change data capture (CDC), and how does it relate to the transactional outbox pattern?hard

    Change data capture reads a database's transaction log, such as the PostgreSQL write-ahead log through logical decoding or the MySQL binlog, and turns each committed insert, update and delete into an event. Debezium, usually running on Kafka Connect, is the best-known open-source tool; the snippet shows a simplified update event.

    Why it's useful:

    • It sees only committed changes, in commit order, with no dual write and no polling load.
    • Applications don't need to change.
    • It feeds search indexes, caches and analytics, and keeps data in sync during a strangler fig migration.

    The catch: streaming raw table changes publishes your internal schema, so consumers couple to column names and a refactor breaks them. That's where the outbox fits: the service writes deliberate domain events to an outbox table in the same transaction as its change, and CDC publishes only that table. Debezium has an outbox event router for this.

    Operationally, watch replication slots (an unconsumed PostgreSQL slot makes the database retain WAL until the disk fills), initial snapshots and schema changes.

    {
      "op": "u",
      "before": { "id": 42, "status": "PENDING" },
      "after": { "id": 42, "status": "PAID" },
      "source": { "connector": "postgresql", "table": "orders", "lsn": 24023128 },
      "ts_ms": 1773483751000
    }
    What interviewers listen for
    • Reads the transaction log, such as WAL or binlog
    • Emits only committed changes, in commit order
    • Raw table events leak the internal schema
    • Outbox plus CDC publishes deliberate domain events
    • Watch replication slots, snapshots and schema changes
  51. 51.Design an order and payment flow across Order, Inventory and Payment services. How do you keep it consistent when a step fails?hard

    I'd model it as an orchestrated saga owned by the Order service, since it has several steps and compensations.

    • Create the order as PENDING in a local transaction, writing OrderCreated to the outbox. Return 202 Accepted with the order id so the client can poll or be notified.
    • Reserve stock: Inventory reserves the items with an expiry; if stock is short, the saga rejects the order.
    • Authorize payment: Payment calls the provider with an idempotency key derived from the order id, so retries can't double-charge. A decline triggers compensation: release the reservation and mark the order REJECTED.
    • Confirm: mark the order CONFIRMED and commit the reservation; capture the payment later, at shipping.

    Cross-cutting safeguards:

    • Every step and compensation is idempotent, and every event goes through an outbox.
    • A payment timeout is an unknown outcome: query the provider's status instead of assuming failure.
    • Provider webhooks arrive at least once, so deduplicate them.
    • A reconciliation job compares orders, reservations and payments, and fixes or flags sagas stuck in an intermediate state.
    What interviewers listen for
    • Orchestrated saga with explicit order states
    • Outbox for events; idempotent steps and compensations
    • Idempotency key on the payment provider call
    • Authorize first, capture later; compensate on decline
    • Treat timeouts as unknown; reconcile stuck sagas

    Likely follow-up: Which step is the pivot transaction in this saga? · How would you handle a customer cancelling after payment?

  52. 52.What makes a good compensating transaction, and how do you deal with the lack of isolation in sagas?hard

    A compensating transaction is a semantic undo, not a rollback. The original change was committed and others may already have seen it, so you apply a new change that reverses its business effect: a refund, a stock release, a cancellation notice.

    Good compensations are:

    • Idempotent and retriable: they may run more than once and must eventually succeed, so retry them and alert a human if they keep failing.
    • Planned with step order in mind: compensatable steps first, then the pivot transaction (the go/no-go point), then steps that are simply retried until they succeed. Irreversible side effects, such as emails, go after the pivot.

    Sagas give you ACD without the I: other transactions can see intermediate state, risking lost updates and dirty reads. Countermeasures:

    • Semantic lock: a state such as APPROVAL_PENDING that tells others the record is mid-saga.
    • Commutative updates that give the same result in any order.
    • Reread value before updating, to detect concurrent changes.
    • Pessimistic view: reorder steps so that a dirty read does the least business harm.
    // Simplified: real orchestrators persist saga state and run steps asynchronously
    List<SagaStep> done = new ArrayList<>();
    try {
        for (SagaStep step : List.of(reserveStock, authorizePayment, confirmOrder)) {
            step.execute(order);
            done.add(step);
        }
    } catch (BusinessFailure e) {
        Collections.reverse(done);                           // undo in reverse order
        for (SagaStep step : done) step.compensate(order);   // idempotent, retried until done
        order.reject(e.reason());
    }
    What interviewers listen for
    • Semantic undo: a new change reversing the business effect
    • Compensations must be idempotent and eventually succeed
    • Compensatable steps, then the pivot, then retriable steps
    • Sagas lack isolation: intermediate state is visible
    • Semantic locks, commutative updates, rereading values
  53. 53.What is a distributed monolith, and how can you tell whether you have built one?mid

    A distributed monolith has the deployment topology of microservices but the coupling of a monolith: you pay for the network and for operating many services without gaining independent deployability. It's arguably worse than either.

    Warning signs:

    • Lockstep releases: changing one feature means deploying several services together, in a specific order.
    • Shared database: services read and write each other's tables.
    • Long synchronous chains: one request calls service after service, so any single failure breaks it.
    • Chatty, fine-grained services split by entity or technical layer rather than business capability.
    • Shared libraries with domain models or business logic that force every service to upgrade together.
    • Breaking API changes that need coordinated work across teams.

    The fixes: redraw boundaries around business capabilities and merge services that always change together; give each service its own data; replace synchronous chains with events and local copies of data; keep shared libraries to technical plumbing; and evolve APIs backward-compatibly, backed by contract tests.

    What interviewers listen for
    • Microservice topology with monolith-style coupling
    • Lockstep deployments and a shared database
    • Long synchronous call chains and chatty services
    • Shared libraries carrying domain logic
    • Fix: redraw boundaries, own data, use events
  54. 54.In a distributed system, how do you design for partial failures and prevent cascading failures?hard

    In a monolith a call either works or the whole process is down. Across a network, a dependency can be slow, down, or have succeeded without you knowing, so I design for each case.

    • Detect: a timeout on every call, sized to the latency budget. A timeout isn't proof of failure; the work may have happened, so retries must be idempotent and ambiguous outcomes need a status check or reconciliation.
    • Contain: bulkheads so one slow dependency can't take every thread, circuit breakers to fail fast, and capped retries with backoff and jitter to avoid retry storms.
    • Degrade: fallbacks such as cached data, defaults or hiding a feature. Decide in advance what may degrade: recommendations can quietly disappear, while a payment check must fail safely with a clear error.
    • Decouple: use asynchronous messaging where no immediate answer is needed, so a downstream outage becomes a backlog instead of an error.
    • Protect yourself: rate limiting and load shedding reject excess work early.

    Then verify it by injecting latency and failures deliberately.

    What interviewers listen for
    • Calls can be slow, fail, or succeed silently
    • Timeouts, with idempotent retries for ambiguous outcomes
    • Bulkheads, circuit breakers and capped retries contain failures
    • Decide up front which features may degrade
    • Async decoupling, load shedding and fault injection
  55. 55.Which metrics would you monitor for a microservice, and how would you alert on them?easy

    For each service I start with request-level metrics, often summarized as RED:

    • Rate: requests per second.
    • Errors: failed requests, as a count and a percentage.
    • Duration: latency as percentiles such as p50, p95 and p99, not averages, because an average hides the slow tail users actually feel.

    For resources, USE: utilization, saturation and errors for CPU, memory, connection pools, thread pools and queues. The "four golden signals" combine the two: latency, traffic, errors and saturation.

    Then service-specific ones: consumer lag for message consumers, circuit breaker state, retry counts, cache hit ratio, and business metrics such as orders per minute, which often catch problems technical metrics miss.

    For alerting, I define SLOs, such as 99.9% of requests succeeding within 300 ms, and alert on user-facing symptoms like error-budget burn rate rather than every CPU spike. Every alert should be actionable and link to a dashboard or runbook. In Java, Micrometer exposes these metrics to Prometheus or via OpenTelemetry.

    What interviewers listen for
    • RED: rate, errors and duration per service
    • Latency as percentiles, not averages
    • USE for resources: pools, queues, CPU, memory
    • Consumer lag, breaker state and business metrics
    • Alert on SLO symptoms such as burn rate
  56. 56.What is Conway's law, and why does it matter for microservices?easy

    Conway's law, named after Melvin Conway, observes that organizations design systems that mirror their own communication structures.

    For microservices it cuts both ways:

    • If teams are split by technical layer (a UI team, a backend team, a database team), you tend to get layered services where every feature needs changes, and coordination, across all of them.
    • If each team owns a business capability end to end, you get services aligned with those capabilities that can change independently.

    So service boundaries and team boundaries should be designed together. The inverse Conway maneuver means shaping teams deliberately to get the architecture you want. Practical guidelines:

    • Each service has exactly one owning team, though a team can own several services.
    • The team builds, deploys and runs its services: "you build it, you run it".
    • Interfaces between teams become explicit APIs and events.

    Microservices are as much an organizational choice as a technical one; without autonomous teams, much of their benefit disappears.

    What interviewers listen for
    • Systems mirror the communication structure of their organization
    • Layer-based teams produce layer-coupled services
    • Align teams with business capabilities end to end
    • Inverse Conway maneuver: shape teams for the architecture
    • One owning team per service; you build it, you run it
  57. 57.Can Kafka give you exactly-once processing? How do idempotent producers and transactions work, and where do their guarantees stop?hard

    Within Kafka, yes, for read-process-write pipelines.

    • Idempotent producer: the broker assigns each producer an id and tracks sequence numbers per partition, so a retried send isn't written twice. Recent clients enable it by default.
    • Transactions: a producer with a transactional.id can write to several partitions and commit the consumer offsets of the input it read, atomically: either the outputs and the offset commit all become visible, or none do.
    • Downstream consumers set isolation.level=read_committed so they never see records from aborted transactions.
    • Kafka Streams wraps all of this behind processing.guarantee=exactly_once_v2.

    The boundary: this covers only state inside Kafka. If processing also writes to a database, calls an API or sends an email, those side effects aren't part of the Kafka transaction and can repeat after a crash. For them you're back to at-least-once plus idempotency: dedupe by message id in the database transaction, use idempotency keys for external calls, or store the consumer offset in the same database transaction as the result.

    spring:
      kafka:
        producer:
          transaction-id-prefix: order-tx-   # enables transactional producers
        consumer:
          isolation-level: read_committed    # skip records from aborted transactions
          enable-auto-commit: false
    What interviewers listen for
    • Idempotent producer dedupes retries with sequence numbers
    • Transactions atomically commit outputs and consumer offsets
    • Consumers read with read_committed isolation
    • The guarantee stops at external side effects
    • Outside Kafka: at-least-once plus idempotency

Prefer multiple choice? All 22 Microservices MCQs with answers →

esc