{"id":1163,"date":"2026-07-17T10:34:49","date_gmt":"2026-07-17T05:04:49","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=1163"},"modified":"2026-07-19T10:54:07","modified_gmt":"2026-07-19T05:24:07","slug":"microservices","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/microservices","title":{"rendered":"Microservices Interview Questions (2026): 25 Must-Know Q&#038;A"},"content":{"rendered":"<p>A backend candidate at a 90-person logistics startup got one question in a March 2026 system design round that ate twenty of the allotted forty-five minutes: what happens to the shipment-confirmation email if the payment service accepts a charge but the notification service is down the moment the event fires. She walked through outbox tables, at-least-once delivery, and why &#8220;exactly once&#8221; is mostly a marketing phrase interviewers still want you to push back on out loud. She moved to the next round. The candidate before her, who defined microservices correctly and then stopped, didn&#8217;t.<\/p>\n<p>Here&#8217;s an opinion that might be wrong: I think most microservices interview prep still treats the split itself as the hard part, when the hard part is everything that happens after the split. An analysis of CNCF&#8217;s fall-2024 annual cloud native survey found 42 percent of organizations were actively consolidating microservices back toward monoliths or modular monoliths (<a href=\"https:\/\/www.softwareseni.com\/the-great-microservices-consolidation-what-the-cncf-2025-survey-reveals-about-industry-trends\/\" target=\"_blank\" rel=\"noopener noreferrer\">SoftwareSeni&#8217;s analysis of the CNCF 2025 survey<\/a>). That&#8217;s not evidence microservices are a bad idea. It&#8217;s evidence a lot of teams adopted them for reasons that had nothing to do with their actual scaling problem, and interviewers in 2026 have gotten noticeably better at probing whether a candidate understands that distinction or just memorized the pattern names.<\/p>\n<p>This page covers the microservices interview questions that come up across backend and platform engineering loops in 2026: monolith-versus-microservices trade-offs, service boundaries, communication, gateways and discovery, resilience, data management, and observability. It&#8217;s organized by topic, since these questions repeat almost verbatim across employers once you&#8217;ve built the mental model once.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">52<\/span><span class=\"iq-stat__label\">Questions<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Distributed Systems<\/span><span class=\"iq-stat__label\">Core Topic<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">System Design + Coding<\/span><span class=\"iq-stat__label\">Rounds<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Saga, CQRS, Circuit Breaker<\/span><span class=\"iq-stat__label\">Pattern Coverage<\/span><\/div><\/div>\n<h2>Monolith vs. microservices: what actually changes<\/h2>\n<p>This is where most microservices interview questions start, even for staff-level candidates, because getting the framing right sets the tone for everything that follows. It&#8217;s a warm-up, but a shaky answer here makes the interviewer dig harder on everything after it.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">11<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the actual difference between a monolith and a microservices architecture?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A monolith deploys as one unit: one codebase, one build, one process (or a cluster of identical processes) that owns the whole application. Microservices split the application into independently deployable services, each owning its own data and its own release cycle, that talk to each other over a network instead of through in-process function calls.<\/p>\n<p>Interviewers use this to check whether a candidate conflates &#8220;microservices&#8221; with &#8220;lots of small files&#8221; or &#8220;using Docker.&#8221; The network boundary is the whole point, not the size of the codebase. A 200-line service is still a microservice if it&#8217;s independently deployed and owns its own data; a 40,000-line module inside one deployable isn&#8217;t, no matter how cleanly it&#8217;s organized internally.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When would you use REST versus gRPC for calls between two internal services?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Communication<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>REST over JSON is the default when you want human-readable payloads, broad tooling support, and easy debugging with curl or a browser. gRPC fits better for internal, high-throughput service-to-service calls where the binary Protobuf encoding and HTTP\/2 multiplexing meaningfully cut latency and payload size, and where both ends of the call are services you control.<\/p>\n<p>The trap: reaching for gRPC because it sounds more serious, then losing the ability to debug an issue quickly because nobody can read a Protobuf-encoded payload off the wire without extra tooling. Public-facing APIs still lean REST almost everywhere for that reason.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the Backend-for-Frontend (BFF) pattern, and why not just have one API gateway serve every client?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Design<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A BFF is a thin API layer built specifically for one client type, say a mobile app, that aggregates and reshapes calls to multiple backend services into the exact shape that one client needs. A single generic gateway serving mobile, web, and a partner API at once tends to grow bloated response payloads and conditional logic trying to satisfy all three, and a change for one client risks breaking the others.<\/p>\n<p>Interviewers ask this to see if you understand that a BFF isn&#8217;t a replacement for the gateway; it usually sits behind it, one BFF per client type, each owned by the team that owns that client, so a mobile-only field change doesn&#8217;t need sign-off from the web team.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the actual difference between a message queue and a pub\/sub topic, and when do you pick one over the other?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Event-Driven Architecture<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A queue (think SQS, or a RabbitMQ queue) delivers each message to exactly one consumer; once it&#8217;s picked up and acknowledged, it&#8217;s gone. A pub\/sub topic (Kafka, SNS, or a RabbitMQ exchange fanning out to multiple queues) broadcasts each message to every subscriber, so five different services can each react to the same &#8220;order placed&#8221; event independently.<\/p>\n<p>Use a queue for work distribution, a pool of workers each picking up one job so no two workers process the same job twice. Use a topic when multiple, unrelated services all need to know the same thing happened, and none of them should have to know the others exist.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does an API gateway actually do that a plain load balancer doesn&#039;t?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Gateway<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A load balancer distributes traffic across identical instances of one service. An API gateway sits in front of many different services and adds cross-cutting concerns, authentication, rate limiting, request routing by path, response aggregation, so individual services don&#8217;t each reimplement the same plumbing.<\/p>\n<p>Interviewers use this to check whether a candidate has actually operated one versus just heard the name. A gateway that also carries heavy business logic is a smell; it&#8217;s supposed to stay thin.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a liveness probe and a readiness probe, and what does Kubernetes actually do when each one fails?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Health Checks<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A liveness probe answers &#8220;is this process still working, or is it stuck.&#8221; If it fails repeatedly, Kubernetes kills the container and restarts it. A readiness probe answers &#8220;should traffic be sent to this instance right now.&#8221; If it fails, Kubernetes just stops routing traffic to that pod; it doesn&#8217;t restart anything.<\/p>\n<p>Mixing these up causes real incidents. Point the liveness probe at a downstream dependency, say, checking the database connection, and a slow database now gets every instance of your service restarted in a loop, which is exactly backwards; a database being slow isn&#8217;t a reason to kill and restart the service that depends on it. The dependency check belongs on readiness, so the pod just gets pulled from rotation until the dependency recovers.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">yaml<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-yaml\">\n\nlivenessProbe:\n\n  httpGet:\n\n    path: \/healthz\/live\n\n    port: 8080\n\n  periodSeconds: 10\n\n  failureThreshold: 3\n\nreadinessProbe:\n\n  httpGet:\n\n    path: \/healthz\/ready\n\n    port: 8080\n\n  periodSeconds: 5\n\n  failureThreshold: 2\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why does &#039;database per service&#039; matter, and what do you give up by enforcing it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Data Management<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Each service owning its own database, with no other service allowed to query it directly, is what actually makes services independently deployable. Without it, a schema change in one service can silently break another service&#8217;s queries, and you&#8217;ve got a distributed system with a monolith&#8217;s coupling hiding inside it.<\/p>\n<p>What you give up is the easy JOIN. Any question that spans two services&#8217; data now needs an API call, an aggregation layer, or a data pipeline instead of one SQL query, and that trade-off is the real cost of the pattern, not a footnote.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why is a single request ID enough in a monolith but not enough across microservices?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Observability<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>In a monolith, one request ID tags one process&#8217;s logs, and that&#8217;s the whole picture. Across microservices, a single user action can fan out into calls to five or ten services, each logging independently, so a trace ID needs to travel along on every downstream call and show up in every one of those services&#8217; logs to reconstruct the full picture.<\/p>\n<p>Without that propagated trace ID, debugging turns into grepping five different log systems by timestamp and hoping the timing lines up, which under load, it often doesn&#8217;t.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s OpenTelemetry, and why has it mostly replaced vendor-specific tracing SDKs?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Observability<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>OpenTelemetry (OTel) is a vendor-neutral standard for generating traces, metrics, and logs, one SDK and one wire format that every backend, Jaeger, Datadog, Honeycomb, whatever your company picks next year, can ingest, instead of instrumenting your code against one vendor&#8217;s proprietary agent and having to re-instrument everything if you switch tools.<\/p>\n<p>Before OTel, switching observability vendors meant ripping out instrumentation code across every service and rewriting it against the new vendor&#8217;s SDK, which is exactly the kind of lock-in cost that made teams stay on a tool they&#8217;d outgrown. Now the instrumentation stays put and only the export destination changes, which is a bigger deal for a company&#8217;s actual flexibility than it sounds on a whiteboard.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does it mean for a microservice to be stateless, and what do you actually do when a service needs to remember something between requests?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Statelessness<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Stateless means the process itself doesn&#8217;t hold onto anything specific to a request once it&#8217;s done responding. No request-specific data sitting in local memory, no in-process session object tied to one user. Because of that, any instance of the service can pick up any incoming request, and you can kill an instance, spin up a new one, or scale from three pods to thirty without losing anything a user cares about. That&#8217;s the whole point: it&#8217;s what makes horizontal scaling and rolling restarts safe.<\/p>\n<p>The catch is that real systems do need to remember things, a shopping cart, a login session, a multi-step checkout flow. The answer is you push that state somewhere external to the process: a Redis cache keyed by user or session ID, a database row, or a signed token like a JWT that the client carries and sends back on every request so the server doesn&#8217;t have to store anything at all. A cart service, for example, shouldn&#8217;t keep the cart in a local hash map, it should write it to Redis on every update so it survives a pod restart and any instance can serve the next request for that user.<\/p>\n<p>The workaround teams sometimes reach for instead is sticky sessions, pinning a user to one specific instance at the load balancer. It works, but it quietly undoes the benefit you split services for in the first place. Now that one instance can&#8217;t be drained or scaled down without dropping active sessions, and your load balancer has to track affinity instead of just spreading load evenly. It&#8217;s a crutch, not a fix.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the risk of sharing a common code library across all your microservices?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Shared libraries<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>It quietly rebuilds the monolith you were trying to get away from, just at the dependency level instead of the deployment level. If every service pulls in the same internal library for, say, order validation logic or a shared &#8220;User&#8221; model, then a change to that library doesn&#8217;t just affect one team, it obligates every service that depends on it to eventually rebuild and redeploy. You&#8217;ve traded a single deployable artifact for a dozen services that all have to move in lockstep whenever that shared code changes, which is most of the coordination cost you split the monolith to avoid.<\/p>\n<p>The more concrete failure mode is version skew. Team A bumps the shared library to pick up a bug fix and redeploys. Team B is mid-sprint and doesn&#8217;t. Now you&#8217;ve got two services in production serializing the same &#8220;domain&#8221; object slightly differently, or applying business rules that diverged the moment one side updated and the other didn&#8217;t. These bugs are nasty because nothing crashes, you just get quietly wrong behavior at the boundary between two services that both think they&#8217;re using the same code.<\/p>\n<p>The practical line most teams draw is: share generic, infrastructure-level code, things like a logging wrapper, a standard HTTP client with retry and tracing headers baked in, or auth token parsing. Don&#8217;t share domain models or business logic across service boundaries, that&#8217;s exactly the coupling that bounded contexts exist to prevent. If two services need the same domain concept, that&#8217;s usually a sign they shouldn&#8217;t have been split in the first place, or that one of them needs to own that concept and expose it through an API instead of a shared class.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">28<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When does splitting a monolith into microservices actually pay off, versus just adding operational overhead?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Architecture Trade-offs<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>It pays off when different parts of the system need to scale independently, are owned by different teams who&#8217;d otherwise block each other on deploys, or carry genuinely different reliability requirements. It adds overhead, and mostly just overhead, when a small team splits a low-traffic app into a dozen services because that&#8217;s what big companies do.<\/p>\n<p>The tell interviewers look for: can you name a concrete scaling or ownership problem the split solves, or are you just repeating the Netflix justification. The &#8220;start with a monolith and split once boundaries are understood from real usage&#8221; argument comes up often enough to be worth having a position on.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you decide where one microservice ends and another begins?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Service Boundaries<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Draw the line around a business capability that changes for its own reasons, not around a technical layer. Orders, payments, and inventory usually end up as separate services because they change on separate release schedules and are owned by separate teams; splitting &#8220;read&#8221; and &#8220;write&#8221; into different services rarely reflects a real boundary on its own.<\/p>\n<p>Weak answers describe boundaries by data model or by technology (&#8220;one service per team&#8217;s favorite framework&#8221;). Interviewers want you reasoning from business change frequency and ownership, the two things that actually predict whether a boundary holds up a year later.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a bounded context, and why does it matter when drawing service boundaries?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Domain-Driven Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A bounded context is a boundary within which a specific domain model and its vocabulary stay consistent. The word &#8220;order&#8221; inside the fulfillment service can mean something subtly different than &#8220;order&#8221; inside the billing service, and that&#8217;s fine as long as each context defines its own meaning clearly.<\/p>\n<p>It matters because forcing one shared Order object across every service is one of the more common causes of tight coupling in a system that&#8217;s supposed to be loosely coupled. Each service should own its own model of a concept, translating at the boundary through an API contract, instead of sharing a single canonical class across service lines.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the strangler fig pattern, and why do most real-world migrations use it instead of a full rewrite?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Migration Strategy<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The strangler fig pattern routes traffic for one specific capability, say the returns workflow, to a new microservice while everything else keeps hitting the old monolith through a facade or reverse proxy. Over months, more capabilities get carved out and rerouted until the monolith has nothing left to do and gets switched off, rather than every team stopping feature work for a year to rewrite from scratch.<\/p>\n<p>The proxy layer is the part interviewers actually want you to describe, since routing has to be correct at the URL or feature level from day one, otherwise a customer bounces between old and new systems mid-session and sees stale data on one side. I&#8217;ve watched a migration stall for eight months because nobody had clearly owned that routing layer, not because carving out the first service was hard.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s Conway&#039;s Law, and how does it actually change how you draw service boundaries?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Team Topology<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Conway&#8217;s Law says a system&#8217;s architecture ends up mirroring the communication structure of the organization that builds it. If two teams sit in separate reporting lines and rarely talk, the software boundary between their pieces hardens along that same line, whether or not it&#8217;s the right technical boundary.<\/p>\n<p>For microservices, this makes the org chart a design constraint, not a footnote. Teams sometimes draw a boundary that looks clean, then find six months in that two &#8220;separate&#8221; services need to change together on every release because one team owns both halves of a workflow split across the wrong seam. Reorganizing teams to match the boundary you actually want, the inverse Conway maneuver, is a real lever, not just a line people quote in a talk.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the real trade-off between synchronous and asynchronous communication in a microservices system?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Communication<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Synchronous calls (REST, gRPC) are simpler to reason about and give an immediate response, but they couple the caller&#8217;s availability to the callee&#8217;s: if the downstream service is slow or down, the caller is too, unless timeouts and fallbacks are built in. Asynchronous messaging decouples that availability at the cost of giving up an immediate answer and taking on eventual consistency.<\/p>\n<p>Interviewers push on this by asking you to pick a specific call in a specific flow, not the whole system, and defend it. &#8220;Everything should be async&#8221; is as wrong an answer as &#8220;everything should be sync.&#8221; A checkout confirmation page probably needs a synchronous inventory check; the email that follows doesn&#8217;t.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s an idempotency key, and why does a retried request need one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Reliability<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An idempotency key is a client-generated identifier attached to a request so that if the network drops the response and the client retries, the server recognizes &#8220;I&#8217;ve already processed this exact request&#8221; and returns the original result instead of, say, charging a customer twice.<\/p>\n<p>This matters because retries are unavoidable in a distributed system. Networks partition, pods restart mid-request, and load balancers occasionally route a retry to a different instance with no memory of the first attempt. Without an idempotency key, &#8220;just retry on failure&#8221; quietly becomes &#8220;sometimes double-charge on failure,&#8221; which is a much worse bug to explain to a customer.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the risk of making everything event-driven instead of using a direct call?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Event-Driven Architecture<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Debuggability. A synchronous call chain shows up as one trace with a clear start and end. An event-driven flow scatters that logic across producers and consumers that never call each other directly, so tracing &#8220;why did this order never ship&#8221; means hunting through five services&#8217; logs with no single request to follow.<\/p>\n<p>My honest take: teams that go all-in on events before they&#8217;ve built the tracing and dead-letter-queue tooling to support it usually regret it within a year. Events fit genuinely decoupled workflows, not every call that feels old-fashioned as a plain request.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem does consumer-driven contract testing solve that end-to-end tests don&#039;t?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Contract Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Consumer-driven contract testing (Pact is the most common tool) lets a consuming service define, in code, exactly which fields and response shapes it depends on from a provider, then verifies the provider against that contract on every build, without either service standing up the other&#8217;s full stack. It catches a breaking change to a shared field the moment the provider team makes it, in their own CI pipeline, instead of three sprints later when a shared end-to-end environment finally gets deployed together.<\/p>\n<p>End-to-end tests still matter, but they&#8217;re slow, flaky across a dozen services, and tell you something broke without pointing at which contract. Contract tests are faster and narrower on purpose; they check the shape of an agreement, not the whole business flow.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">json<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-json\">\n\n{\n\n  \u201cconsumer\u201d: \u201ccheckout-service\u201d,\n\n  \u201cprovider\u201d: \u201cinventory-service\u201d,\n\n  \u201cinteractions\u201d: [\n\n    {\n\n      \u201cdescription\u201d: \u201cget stock for sku CHR-2291\u201d,\n\n      \u201crequest\u201d: { \u201cmethod\u201d: \u201cGET\u201d, \u201cpath\u201d: \u201c\/inventory\/CHR-2291\u201d },\n\n      \u201cresponse\u201d: {\n\n        \u201cstatus\u201d: 200,\n\n        \u201cbody\u201d: { \u201csku\u201d: \u201cCHR-2291\u201d, \u201cavailable\u201d: 42 }\n\n      }\n\n    }\n\n  ]\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a dead-letter queue actually for, and what should happen to a message that lands in one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Event-Driven Architecture<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A dead-letter queue (DLQ) catches messages that failed processing after the configured number of retries, so a poison message, one with a bug-triggering payload that will never succeed no matter how many times it&#8217;s retried, doesn&#8217;t block every other message stuck behind it in the same queue.<\/p>\n<p>The mistake teams make is treating the DLQ as a place messages go to disappear quietly. Someone needs to alert on DLQ depth and actually look at what landed there; otherwise a bug that&#8217;s been silently dropping every fifth order confirmation for two weeks doesn&#8217;t surface until a customer complains, and by then reprocessing means reconstructing state from logs instead of just replaying the queue.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Kafka guarantees message ordering. True or false, and why does the honest answer matter for how you key your messages?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Message Ordering<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>False, or at least incomplete. Kafka only guarantees order within a single partition, not across an entire topic. Two events for the same order landing on different partitions can be processed out of order relative to each other, even though each partition individually stays strictly ordered.<\/p>\n<p>That&#8217;s why the partition key matters more than most candidates expect going in: keying by order ID sends every event for that one order to the same partition, preserving the order that actually matters, while spreading load across the topic&#8217;s other partitions for unrelated orders. Pick the wrong key, say a constant value, and you get perfect ordering with zero parallelism, which defeats the reason you picked Kafka in the first place.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">API gateway versus service mesh, what&#039;s the actual difference, and do you need both?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Service Mesh<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A gateway manages north-south traffic, requests coming into the system from outside. A service mesh (Istio, Linkerd) manages east-west traffic, service-to-service calls inside the cluster, adding retries, mutual TLS, and observability at the network layer through sidecar proxies instead of application code.<\/p>\n<p>Plenty of systems run both, and plenty run just a gateway for years before a mesh is worth the added complexity. The honest answer to &#8220;do you need a mesh&#8221; is usually not yet, not until you&#8217;ve got enough services that manually wiring retries and mTLS into each one has become its own maintenance burden.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does service discovery work when service instances scale up and down constantly?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Service Discovery<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Each instance registers its network address with a discovery service (Consul, Eureka, or Kubernetes&#8217; built-in DNS-based discovery) on startup, and deregisters, or gets removed via a health check failure, on shutdown. Callers ask the discovery service, or a client-side load balancer caching its state, for a current instance list instead of hardcoding an address.<\/p>\n<p>Kubernetes mostly hides this behind a Service object and cluster DNS now, part of why fewer candidates hand-roll a Eureka client the way they did five years ago. Interviewers still expect you to explain what&#8217;s underneath, not just say &#8220;Kubernetes handles it.&#8221;<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Client-side load balancing versus server-side, what&#039;s the actual trade-off?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Service Discovery<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Server-side load balancing puts a dedicated component (a load balancer, or a gateway) between the caller and the pool of instances; the caller just calls one address and the balancer picks an instance. Client-side load balancing has the caller itself pull the current instance list from a discovery service and pick one directly, no extra hop in between.<\/p>\n<p>Client-side (Netflix&#8217;s old Ribbon, or a service-mesh sidecar acting on the caller&#8217;s behalf now) shaves off a network hop and lets each caller make its own retry and failover decisions, but it pushes that logic, and the list-freshness problem, into every single caller instead of one shared place. Most teams running Kubernetes get this for free through kube-proxy and cluster DNS without consciously picking a side, which is part of why fewer candidates can explain the trade-off from first principles now.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a circuit breaker, and what do its three states actually do?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Circuit Breaker<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A circuit breaker wraps a call to a potentially failing dependency and tracks its failure rate. Closed means calls flow through normally. Open means the breaker has seen enough failures that it stops calling the dependency entirely and fails fast instead, protecting both the caller&#8217;s threads and the already-struggling dependency from more load. Half-open lets a small number of test calls through after a cooldown to check whether the dependency has recovered.<\/p>\n<p>The part candidates forget: failing fast is the actual point, not a side effect. Without a breaker, a caller&#8217;s threads pile up waiting on a slow dependency until the caller runs out of capacity too, turning one service&#8217;s outage into two.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">yaml<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-yaml\">\n\nresilience4j:\n\n  circuitbreaker:\n\n    instances:\n\n      inventoryService:\n\n        failureRateThreshold: 50\n\n        slidingWindowSize: 20\n\n        waitDurationInOpenState: 15s\n\n        permittedNumberOfCallsInHalfOpenState: 5\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Retries sound simple. What actually goes wrong if you retry a failed call without thinking it through?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Retries<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Naive retries turn one slow dependency into a self-inflicted denial-of-service attack on it. If every caller retries immediately, three times, the moment a dependency gets slow, the load on the already-struggling thing has just roughly quadrupled, which is exactly backwards.<\/p>\n<p>The fix is exponential backoff with jitter, so retries spread out instead of arriving in synchronized waves, plus a cap on total attempts and, ideally, a circuit breaker that stops retries altogether once the dependency is clearly down rather than just slow.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\nint attempt = 0;\n\nlong baseDelayMs = 200;\n\nwhile (attempt &lt; 4) {\n\n    try {\n\n        return callDownstream();\n\n    } catch (TransientException e) {\n\n        long backoff = baseDelayMs * (1L &lt;&lt; attempt);\n\n        long jitter = ThreadLocalRandom.current().nextLong(backoff \/ 2);\n\n        Thread.sleep(backoff + jitter);\n\n        attempt++;\n\n    }\n\n}\n\nthrow new RetriesExhaustedException();\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s graceful degradation, and how is it different from just failing fast with a circuit breaker?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Resilience<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Failing fast means the caller gets an error quickly instead of waiting on a dependency that&#8217;s down. Graceful degradation goes a step further: instead of an error, the caller returns something useful with reduced quality, cached recommendations instead of live ones, a product page without reviews instead of no page at all, so the user barely notices anything&#8217;s wrong.<\/p>\n<p>Not every feature deserves this treatment, and building a fallback path for everything is its own maintenance cost nobody talks about enough. It&#8217;s worth it for the features on the critical path to checkout or the homepage; it&#8217;s usually not worth it for an admin-only reporting screen that three internal people check once a day.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A dependency&#039;s p99 latency is 800ms, so someone sets the caller&#039;s timeout to 2 seconds to be safe. What&#039;s wrong with that?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Resilience<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A 2-second timeout, when the dependency&#8217;s actual p99 is 800ms, means a genuinely stuck call ties up the caller&#8217;s thread for the full 2 seconds before giving up, which under load is plenty of time for a small number of stuck requests to exhaust the caller&#8217;s entire thread pool and take down a service that isn&#8217;t even the one having trouble.<\/p>\n<p>The safer number is closer to the dependency&#8217;s own p99 or p99.9, plus a small margin, not a round number picked to feel comfortable. Pair it with a circuit breaker so repeated timeouts stop hammering a dependency that&#8217;s clearly down, and revisit the number when the dependency&#8217;s latency profile changes, since a timeout set correctly for last year&#8217;s traffic pattern isn&#8217;t automatically still correct.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s chaos engineering, and why deliberately break a production system instead of just testing failure cases in staging?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Chaos Engineering<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Chaos engineering runs controlled experiments, killing an instance, injecting latency, cutting a service off from a dependency, against a live system to check whether the resilience patterns you built (retries, circuit breakers, fallbacks) actually work under real traffic and real data, not just the traffic pattern a staging environment happens to have.<\/p>\n<p>Staging rarely has production&#8217;s traffic volume, its noisy-neighbor effects, or its actual data shape, so a fallback path that looks fine in staging can still fail in a way nobody predicted once real load hits it. Netflix&#8217;s Chaos Monkey popularized this by randomly killing instances during business hours; most teams start smaller, one controlled experiment during a low-traffic window with an easy rollback, not a random killer running unsupervised from day one.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the saga pattern, and how is it actually different from a two-phase commit?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Saga Pattern<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A saga is a sequence of local transactions, each in a different service, where every step has a matching compensating action to undo it if a later step fails. Two-phase commit tries to make the whole sequence atomic across services using a coordinator and a prepare-then-commit protocol; sagas give up atomicity and use compensation instead (<a href=\"https:\/\/microservices.io\/patterns\/data\/saga.html\" target=\"_blank\" rel=\"noopener noreferrer\">Chris Richardson&#8217;s saga pattern reference<\/a>).<\/p>\n<p>2PC mostly doesn&#8217;t survive contact with real microservices because it needs a coordinator holding locks while every participant waits, exactly the cross-service coupling you split the system to avoid. Sagas trade strict atomicity for availability, usually the right trade.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem does CQRS actually solve, and when is it overkill?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CQRS<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>CQRS splits the model used to write data from the model used to read it, letting each be optimized separately: a normalized write model for consistency, a denormalized read model tuned for the exact queries the UI actually runs. It&#8217;s a real answer for read-heavy systems where the write and read shapes have diverged enough that one shared model serves neither well.<\/p>\n<p>It&#8217;s overkill for a CRUD service where reads and writes look basically the same. Interviewers want you to name the actual symptom, slow reads from complex joins, or a reporting view fighting the transactional schema, not just say &#8220;CQRS&#8221; because it sounds advanced.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Give a concrete example, not the textbook definition, of choosing availability over consistency in a real microservices system.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Data Management<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A shopping cart service during a network partition between two data center regions. Rather than reject every write until the partition heals, and lose sales, the service keeps accepting writes to whichever regional replica the customer&#8217;s request reaches, then reconciles the two carts once the partition clears, occasionally merging or dropping a duplicate line item.<\/p>\n<p>That&#8217;s choosing availability and accepting weaker consistency, because the business cost of an unavailable cart page during checkout is worse than the cost of an occasional merge conflict a customer barely notices. Inventory counts usually make the opposite choice; overselling a physical item is expensive enough that some inventory services would rather reject a write than risk a duplicate sale (<a href=\"https:\/\/www.infoq.com\/articles\/cap-twelve-years-later-how-the-rules-have-changed\/\" target=\"_blank\" rel=\"noopener noreferrer\">Eric Brewer&#8217;s own retrospective on CAP, twelve years later<\/a> is worth reading if this comes up).<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Marketing wants a single report joining order data, inventory data, and customer data, each owned by a different service. How do you build that without breaking database-per-service?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Analytics<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>You don&#8217;t query three services&#8217; databases directly from a report; that&#8217;s the exact coupling database-per-service exists to prevent. Instead, each service publishes the events or a change stream that matters, order placed, inventory adjusted, customer updated, into a data warehouse or a dedicated analytics store, where the join happens on data that&#8217;s already been copied out and denormalized for reporting.<\/p>\n<p>This means the report is eventually consistent, usually by minutes, not real time, and that&#8217;s almost always fine for a marketing dashboard. It&#8217;s a genuinely different problem from an operational query a live checkout page needs answered in milliseconds, and conflating the two, trying to make one system serve both, is where a lot of &#8220;just add a read replica&#8221; plans quietly fall apart.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the actual difference between a log, a metric, and a trace, and why do you need all three?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Observability<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A log is a discrete, timestamped event with detail, one line saying exactly what happened. A metric is a number aggregated over time, request count, error rate, p99 latency, cheap to store and good for alerting on trends. A trace follows one request&#8217;s path across every service it touched, with timing for each hop.<\/p>\n<p>Metrics tell you something&#8217;s wrong. Traces tell you where. Logs tell you why, once you&#8217;ve narrowed it down. Skipping any one of the three means missing a step in that chain, and interviewers ask this to check whether you&#8217;ve actually used all three together to solve a real incident, not just installed a dashboard.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">json<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-json\">\n\n{\n\n  \u201ctimestamp\u201d: \u201c2026-03-14T09:12:03.114Z\u201d,\n\n  \u201clevel\u201d: \u201cerror\u201d,\n\n  \u201cservice\u201d: \u201cpayment-service\u201d,\n\n  \u201ctrace_id\u201d: \u201c4bf92f3577b34da6a3ce929d0e0e4736\u201d,\n\n  \u201cspan_id\u201d: \u201c00f067aa0ba902b7\u201d,\n\n  \u201cmessage\u201d: \u201cdownstream call to inventory-service timed out after 3000ms\u201d\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">An alert fires that p99 latency on checkout jumped. How do you find which of the dozen services in the call chain is responsible?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Observability<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Pull the trace for a slow request and look at each span&#8217;s own duration, not the total. The service whose own span time, not counting time spent waiting on its children, blew up is the one that regressed; everything upstream of it just looks slow because it&#8217;s waiting.<\/p>\n<p>Without tracing wired end to end, this turns into checking each service&#8217;s own dashboard one at a time and guessing at the correlation. That gap, microservices adopted before tracing, is where a lot of 3am incidents drag on far longer than they need to.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s an error budget, and how does it actually change what an on-call engineer does day to day?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Observability<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An SLO, say 99.9 percent of requests succeed over 30 days, implies an error budget, the 0.1 percent of requests allowed to fail without breaking the promise. Once a team has burned through that budget for the month, the honest answer to &#8220;should we ship this risky feature today&#8221; changes from &#8220;sure&#8221; to &#8220;no, we&#8217;re out of room, fix reliability first.&#8221;<\/p>\n<p>Without an explicit budget, reliability work competes against feature work purely on vibes, and features usually win that argument by default. With a budget, running out of it is a concrete, pre-agreed trigger to freeze risky releases, not a debate that has to be re-litigated every time. Google&#8217;s SRE book is where most teams pull the framing from, and the actual math, 99.9 percent over 30 days is about 43 minutes of allowed downtime, is worth knowing cold, not just the concept.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you deploy a breaking API change to a service other teams&#039; services depend on, without everyone releasing in lockstep?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Versioning<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Version the API and run both versions side by side for a deprecation window, v1 and v2 as separate routes or a version header, rather than changing v1&#8217;s behavior under callers who haven&#8217;t migrated yet. Give consumers a real deadline and a migration guide, then remove the old version once usage telemetry shows nobody&#8217;s calling it anymore, not on a guess.<\/p>\n<p>The failure mode interviewers probe for is a team that changes a field&#8217;s meaning in place, &#8220;we didn&#8217;t add a new endpoint, we just made the existing one smarter,&#8221; which breaks every caller who hasn&#8217;t read the changelog, and there usually isn&#8217;t a changelog.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Canary deployment versus blue-green, what&#039;s the actual trade-off, and which catches a bad release earlier?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Deployment<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Blue-green keeps two full environments running, &#8220;blue&#8221; (current) and &#8220;green&#8221; (new), and cuts over all traffic at once after the new version passes its checks; rolling back means flipping the switch back to blue, fast, but a bug that only shows up under real production traffic still hits 100 percent of users the moment you cut over.<\/p>\n<p>Canary sends a small slice of real traffic, often 5 percent, to the new version first, watches error rates and latency against the baseline, and only ramps up if the metrics hold. It catches a bad release with far fewer affected users, since 5 percent of production traffic saw the bug instead of everyone, but it needs good enough metrics and automation to actually watch that 5 percent and decide, rather than a person eyeballing a dashboard for twenty minutes and guessing it&#8217;s fine.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">13<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Give an example of a service boundary that looks right on a whiteboard but causes problems in production.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Service Boundaries<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Splitting &#8220;user profile&#8221; and &#8220;user preferences&#8221; into two services is the classic one. It looks clean on a diagram, but almost every feature that touches a user needs both, so you end up with chatty synchronous calls between the two on nearly every request, plus two places to keep consistent when a user is deleted or merged.<\/p>\n<p>The fix usually isn&#8217;t recombining them; it&#8217;s asking whether preferences change often enough, and independently enough, to justify the network hop at all. I&#8217;ve seen teams merge these back within six months more often than I&#8217;ve seen them stay split. Not a universal rule, just a pattern that shows up a lot.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s an anti-corruption layer, and when do you actually need one between two services?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Domain-Driven Design<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An anti-corruption layer is a translation layer at a service boundary that converts another service&#8217;s, or a legacy system&#8217;s, model into your own service&#8217;s vocabulary, so a messy or unstable upstream model never leaks directly into your domain logic. You write an adapter once, at the edge, instead of scattering &#8220;if this field means X over there&#8221; checks through every place your service touches that data.<\/p>\n<p>It matters most when integrating with a legacy monolith you don&#8217;t control, or a third-party API whose data model doesn&#8217;t map cleanly onto your own bounded context. Skipping it feels like it saves time early, since it&#8217;s one less class to write, but it&#8217;s usually the reason a supposedly independent service ends up needing a code change every time the upstream system renames a field.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A request times out at the edge after 3 seconds, but a service four hops deep keeps running for another 8. What&#039;s missing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Reliability<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Deadline propagation. The initial timeout needs to travel with the request as a shrinking budget, not just apply once at the entry point, so each downstream service knows how much time is actually left and can bail out early instead of finishing work nobody&#8217;s waiting for anymore.<\/p>\n<p>Without it, a caller gives up and returns an error to the user while three more services keep burning CPU and connection-pool slots on a request whose result already got thrown away. gRPC has this built in through context deadlines; plenty of REST-based systems just don&#8217;t bother, which is part of why a slow edge timeout doesn&#8217;t always match up with what&#8217;s actually happening downstream.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">go<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-go\">\n\nctx, cancel := context.WithTimeout(parentCtx, remainingBudget)\n\ndefer cancel()\n\nresp, err := inventoryClient.CheckStock(ctx, req)\n\nif ctx.Err() == context.DeadlineExceeded {\n\n    return nil, ErrUpstreamBudgetExhausted\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the actual difference between at-least-once, at-most-once, and exactly-once delivery, and which one do brokers actually give you?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Message Delivery<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>At-most-once means a message might get dropped if anything fails; nobody retries, so no duplicates, but data can silently vanish. At-least-once means the broker retries until it gets an acknowledgment, so a message might arrive twice if the ack itself gets lost, but it never silently disappears. Exactly-once is the one everyone wants, and almost no broker actually gives it to you end to end.<\/p>\n<p>Kafka and most managed queues default to at-least-once, and the &#8220;exactly-once&#8221; support that does exist, Kafka&#8217;s transactional producer, for instance, usually only guarantees it within that one system, not across the whole chain once your consumer also writes to its own database. In practice, exactly-once processing gets built at the application layer through idempotency keys and deduplication tables, on top of an at-least-once broker, not by trusting the broker alone.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s event sourcing, and how is it different from just publishing domain events or using CQRS?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Event Sourcing<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Event sourcing stores every state change as an immutable, ordered event, &#8220;OrderPlaced,&#8221; &#8220;OrderShipped,&#8221; and rebuilds current state by replaying that event log, instead of storing only the current row and overwriting it on every update. A regular service with CQRS or domain events still keeps a normal table as its source of truth and publishes events as a side effect; event sourcing makes the event log itself the source of truth.<\/p>\n<p>The payoff is a full audit trail for free and the ability to answer &#8220;what did this order look like last Tuesday&#8221; without a separate history table. The cost is real: replaying thousands of events to rebuild current state gets slow without snapshotting, and a bug in an early event&#8217;s shape can be painful to fix once years of history depend on reading it a certain way. It&#8217;s a genuinely good fit for finance and audit-heavy domains, and overkill for a simple CRUD service that just needs an event fired when something changed.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Mechanically, how does a sidecar proxy add mTLS between two services without either service&#039;s code changing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Service Mesh<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The mesh control plane injects a proxy container (Envoy, in Istio&#8217;s case) into the same pod as the application container, and reconfigures the pod&#8217;s networking so all inbound and outbound traffic gets routed through that proxy first, transparently, via iptables rules set at pod startup. The application still just calls &#8220;http:\/\/inventory-service&#8221; like nothing changed; the sidecar intercepts that call, wraps it in mutual TLS using a certificate the control plane issued to that specific workload, and hands it off to the destination pod&#8217;s sidecar, which decrypts it before the application container ever sees the request.<\/p>\n<p>The certs get rotated automatically, often every 24 hours, by the control plane, which is the actual point: nobody&#8217;s manually managing a cert store per service. The honest trade-off is added latency per hop, usually single-digit milliseconds, and a genuinely harder debugging story the first time a sidecar itself misbehaves and it&#8217;s not obvious whether the app or the proxy is the problem.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the bulkhead pattern, and what specific failure does it prevent?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Bulkhead<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Bulkheading isolates resources, thread pools, connection pools, or entire instances, per dependency, so a slow or failing dependency can&#8217;t exhaust the resources every other call in the system also needs. The name comes from ship design: a flooded compartment sinks that compartment, not the whole ship.<\/p>\n<p>Without it, a single slow call can eat every thread in a shared pool, and an unrelated feature that never touches that dependency starts timing out too. I don&#8217;t have a clean rule for how many pools to carve out; too many and you&#8217;ve fragmented capacity into pools that individually run dry even with headroom system-wide.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Choreography-based saga versus orchestration-based saga, which do you pick and why?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Saga Pattern<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Choreography has each service publish an event when it finishes its step, and the next service reacts to that event with no central coordinator. Orchestration has a dedicated orchestrator service that explicitly tells each step what to do next and handles compensation logic centrally.<\/p>\n<p>Choreography scales better with fewer services because there&#8217;s no single owner to become a bottleneck, but it gets genuinely hard to reason about past four or five steps, since the workflow is implicit, scattered across every service&#8217;s event handlers instead of written down in one place. Orchestration costs you a new component to build and operate, but it makes the business process readable in one file instead of reverse-engineered from event subscriptions.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through a concrete bug caused by assuming strong consistency across services, and how the outbox pattern fixes it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Eventual Consistency<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Classic version: a service updates its own database and then publishes an event about that update as two separate steps. If the process crashes between the commit and the publish, the event never fires, and every downstream service that should have reacted, sending a confirmation email, updating a search index, silently never does, with no error anywhere to point at.<\/p>\n<p>The outbox pattern fixes it by writing the event into an outbox table in the same local transaction as the actual data change, so both succeed or fail together. A separate poller, or a change-data-capture process reading the database log, reads that table and publishes the events after the fact, guaranteeing the event eventually goes out if the data change committed at all.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">json<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-json\">\n\n{\n\n  \u201cid\u201d: \u201c6f2e1c1a-6f0c-4e7c-8a2e-6a8d6f42a3f1\u201d,\n\n  \u201caggregate_type\u201d: \u201cOrder\u201d,\n\n  \u201caggregate_id\u201d: \u201cord_88213\u201d,\n\n  \u201cevent_type\u201d: \u201cOrderConfirmed\u201d,\n\n  \u201cpayload\u201d: { \u201corder_id\u201d: \u201cord_88213\u201d, \u201ctotal_cents\u201d: 4599 },\n\n  \u201ccreated_at\u201d: \u201c2026-03-14T09:12:03Z\u201d,\n\n  \u201cpublished_at\u201d: null\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How is log-based change data capture (CDC) different from the outbox pattern, if they&#039;re solving a similar problem?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Data Management<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The outbox pattern still requires the service&#8217;s own code to write a row into an outbox table inside the same transaction as the data change. Log-based CDC, using something like Debezium, skips that step; it reads the database&#8217;s own write-ahead log, or binlog for MySQL, directly and turns every row change into an event, without the application ever writing to a dedicated table on purpose.<\/p>\n<p>That means CDC works even against a legacy database whose application code you can&#8217;t touch, which the outbox pattern can&#8217;t do, since it needs code changes inside the service. The trade-off is that CDC exposes every row change, including ones that were never meant to be a public event, so teams usually still need a transformation step between the raw CDC stream and whatever a downstream service actually consumes, or they end up leaking internal schema details as an accidental public contract.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">json<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-json\">\n\n{\n\n  \u201cname\u201d: \u201corders-connector\u201d,\n\n  \u201cconfig\u201d: {\n\n    \u201cconnector.class\u201d: \u201cio.debezium.connector.mysql.MySqlConnector\u201d,\n\n    \u201cdatabase.hostname\u201d: \u201corders-db.internal\u201d,\n\n    \u201cdatabase.include.list\u201d: \u201corders\u201d,\n\n    \u201ctable.include.list\u201d: \u201corders.order_line_items\u201d,\n\n    \u201ctopic.prefix\u201d: \u201ccdc.orders\u201d\n\n  }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why are distributed locks (say, a Redis-based lock) riskier than they look, and what&#039;s the actual failure mode?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Distributed Systems<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A distributed lock is only as good as its expiry and its owner check, and both are easy to get subtly wrong. If a service acquires a lock, gets paused by a garbage collection stop-the-world pause longer than the lock&#8217;s expiry, and resumes after the lock has already been handed to someone else, it keeps working as if it still holds the lock, because nothing told it otherwise.<\/p>\n<p>That&#8217;s the classic split-brain scenario, two processes both believing they hold the same lock at the same time and both writing, which is worse than no lock at all since it happens silently. Redlock, Redis&#8217;s own proposed algorithm for this, has been publicly disputed (<a href=\"https:\/\/martin.kleppmann.com\/2016\/02\/08\/how-to-do-distributed-locking.html\" target=\"_blank\" rel=\"noopener noreferrer\">Martin Kleppmann&#8217;s detailed critique of its safety guarantees<\/a>) precisely because distributed locking looks straightforward until you account for clock drift and process pauses. My honest take: reach for a distributed lock only after checking whether the problem can be solved with a database-level unique constraint or a compare-and-swap instead, since either one is usually safer and a lot less code.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the most common mistake teams make on their first microservices migration?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Common Pitfalls<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Splitting along technical layers, one service for database access, one for business logic, one for the API, instead of along business capability. It produces a dozen services that still deploy together anyway, one logical unit wearing ten network hops as a costume, all the complexity with none of the independent-deployment benefit.<\/p>\n<p>The second most common mistake, close behind: skipping distributed tracing and centralized logging until after the first painful incident, because it felt like work that could wait. It never should have. Both trace back to the same root cause: splitting before doing the design work that&#8217;s supposed to justify it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you rename a column a service&#039;s database depends on without downtime, when other instances of the same service are still deploying against the old schema?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Database Migration<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Expand-contract, sometimes called parallel change. First, expand: add the new column alongside the old one, and have the application write to both on every write path, while still reading from the old one. Deploy that, let it run until you&#8217;re confident writes are landing correctly on both.<\/p>\n<p>Then switch reads over to the new column, deploy, verify. Only after that, contract: stop writing to the old column, and drop it in a later migration once nothing references it anymore. Skipping straight to a single migration that renames the column in place is the version that goes wrong, because the moment the schema changes, every instance still running the previous deploy, and there always are some during a rolling deployment, starts failing every query that touches that column.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">sql<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-sql\">\n\n\u2014 Step 1: expand\n\nALTER TABLE customers ADD COLUMN email_address VARCHAR(255);\n\nUPDATE customers SET email_address = email WHERE email_address IS NULL;\n\n\u2014 app writes to both `email` and `email_address` here\n\u2014 Step 2: after reads are verified against email_address, switch reads over\n\u2014 Step 3: contract, once nothing references the old column\n\nALTER TABLE customers DROP COLUMN email;\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<h2>How to prepare for microservices interview questions in 2026<\/h2>\n<p>Reading pattern names doesn&#8217;t help much on its own. Build one small system, an orders service, an inventory service, and a notifications service talking over a queue, and deliberately break it: kill the inventory service mid-request and watch what happens to the caller. Fix it with a timeout, then a circuit breaker, then compare. That sequence teaches the trade-off in a way reading a definition never does.<\/p>\n<p>Across mock system design sessions run through LastRoundAI&#8217;s backend and infrastructure track, the follow-up that catches the most candidates on microservices interview questions isn&#8217;t naming a pattern. It&#8217;s defending a specific trade-off under a changed constraint, &#8220;fine, now assume that queue can lose messages, what changes.&#8221; We don&#8217;t have a clean percentage to put on that, only that it comes up often enough in review to be worth flagging here. Candidates who&#8217;ve actually operated a distributed system, even a small side project, handle that follow-up noticeably better than candidates who&#8217;ve only read about one.<\/p>\n<h2>Naming the pattern isn&#8217;t the same as defending it<\/h2>\n<p>Reading a saga pattern definition off this page covering microservices interview questions is not the same as explaining it live once an interviewer changes one assumption on you mid-answer. <a href=\"\/products\/mock-interviews\">LastRoundAI&#8217;s mock interview mode<\/a> runs system design rounds with real-time follow-up questions instead of a static script, and the free plan includes 15 credits a month that reset monthly. Starter is $19\/mo if you want more sessions than that covers.<\/p>\n<p>If finding enough backend or platform engineering openings is the harder part right now, <a href=\"\/products\/auto-apply\">Auto-Apply<\/a> matches and applies to roles for you, 10 a month on the free plan, up to 400 a month on the Ultimate plan, with every application held in a review queue until you approve it. Nothing goes out on your behalf without you seeing it first. There&#8217;s no native mobile app yet, just a desktop app and a browser that works fine from a phone.<\/p>\n<p>Questions about either product go to contact@lastroundai.com. That&#8217;s the only inbox we check.<\/p>\n<div class=\"iq-sources not-prose\"><div class=\"iq-sources__title\">Sources &amp; further reading<\/div><ul class=\"iq-sources__list\"><li><a href=\"https:\/\/www.softwareseni.com\/the-great-microservices-consolidation-what-the-cncf-2025-survey-reveals-about-industry-trends\/\" target=\"_blank\" rel=\"nofollow noopener\">CNCF 2025 Annual Survey analysis: microservices consolidation<\/a><\/li><li><a href=\"https:\/\/martinfowler.com\/bliki\/CircuitBreaker.html\" target=\"_blank\" rel=\"nofollow noopener\">Martin Fowler: CircuitBreaker<\/a><\/li><li><a href=\"https:\/\/microservices.io\/patterns\/data\/saga.html\" target=\"_blank\" rel=\"nofollow noopener\">Chris Richardson, microservices.io: Saga pattern<\/a><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>A backend candidate at a 90-person logistics startup got one question in a March 2026 system design round that ate twenty of the allotted forty-five minutes: what happens to the shipment-confirmation email if the payment service accepts a charge but the notification service is down the moment the event fires. She walked through outbox tables,&#8230;<\/p>\n","protected":false},"author":7,"featured_media":1692,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-1163","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Microservices Interview Questions (2026) | LastRoundAI<\/title>\n<meta name=\"description\" content=\"Microservices interview questions for 2026: service boundaries, gateways, circuit breakers, sagas, and CQRS, with the follow-ups interviewers actually ask.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/microservices\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Microservices Interview Questions (2026) | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"Microservices interview questions for 2026: service boundaries, gateways, circuit breakers, sagas, and CQRS, with the follow-ups interviewers actually ask.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/microservices\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-19T05:24:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-microservices-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices\",\"name\":\"Microservices Interview Questions (2026) | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-microservices-og.png\",\"datePublished\":\"2026-07-17T05:04:49+00:00\",\"dateModified\":\"2026-07-19T05:24:07+00:00\",\"description\":\"Microservices interview questions for 2026: service boundaries, gateways, circuit breakers, sagas, and CQRS, with the follow-ups interviewers actually ask.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-microservices-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-microservices-og.png\",\"width\":1200,\"height\":630,\"caption\":\"Microservices interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/microservices#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Microservices Interview Questions (2026): 25 Must-Know Q&#038;A\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Microservices Interview Questions (2026) | LastRoundAI","description":"Microservices interview questions for 2026: service boundaries, gateways, circuit breakers, sagas, and CQRS, with the follow-ups interviewers actually ask.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/microservices","og_locale":"en_US","og_type":"article","og_title":"Microservices Interview Questions (2026) | LastRoundAI","og_description":"Microservices interview questions for 2026: service boundaries, gateways, circuit breakers, sagas, and CQRS, with the follow-ups interviewers actually ask.","og_url":"https:\/\/lastroundai.com\/interview-questions\/microservices","og_site_name":"LastRound AI","article_modified_time":"2026-07-19T05:24:07+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-microservices-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/microservices","url":"https:\/\/lastroundai.com\/interview-questions\/microservices","name":"Microservices Interview Questions (2026) | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/microservices#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/microservices#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-microservices-og.png","datePublished":"2026-07-17T05:04:49+00:00","dateModified":"2026-07-19T05:24:07+00:00","description":"Microservices interview questions for 2026: service boundaries, gateways, circuit breakers, sagas, and CQRS, with the follow-ups interviewers actually ask.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/microservices#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/microservices"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/microservices#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-microservices-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-microservices-og.png","width":1200,"height":630,"caption":"Microservices interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/microservices#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"Microservices Interview Questions (2026): 25 Must-Know Q&#038;A"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1163","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=1163"}],"version-history":[{"count":4,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1163\/revisions"}],"predecessor-version":[{"id":1755,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1163\/revisions\/1755"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/1692"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=1163"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=1163"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}