Every system with more than a couple of services eventually needs something in front of them, and the architecture questions in this space separate candidates who can draw a box labeled "gateway" from those who have owned one through a security incident, a release that broke every client at once, or a gateway that quietly became the thing every team had to route their pull requests through. At the architect level, expect scrutiny on exactly which responsibilities belong at the edge versus in services, how a Backend-for-Frontend keeps tokens away from browser JavaScript, and how to stop a gateway from becoming an unowned monolith. The ten questions below cover gateway responsibilities, YARP versus managed alternatives, BFF token handling, aggregation trade-offs and cross-cutting concerns.

Q1 What responsibilities actually belong in an API gateway, and which ones should stay in the services behind it?#

Short answer: A gateway owns cross-cutting, edge-level concerns that are the same for every request regardless of which service handles it: routing, TLS termination, authentication, coarse-grained rate limiting, and request/response shaping such as header rewriting. Business logic, authorization decisions that depend on domain state, and data validation belong in the services, because pushing them to the gateway couples every team's domain rules to a shared, harder-to-change piece of infrastructure.

The test that keeps this boundary honest: would this logic be identical no matter which backend service handled the request? TLS termination, yes — logging format, yes — but "is this user allowed to discount this specific order," no, because that depends on domain state the gateway shouldn't need to know. A gateway can still do coarse authorization, such as rejecting an unauthenticated request before it reaches a service at all, without owning the fine-grained, resource-specific authorization decision that only the owning service has the context to make correctly.

What interviewers look for: a boundary test stated explicitly, not a memorized list; awareness that coarse authentication at the edge and fine-grained authorization in the service are different things.

Common mistakes: putting domain-specific business rules at the gateway "because it's easy to change without a service deploy," which actually makes it harder to change safely, since it now affects every service behind that route.

Follow-up questions:

  • Where would you put request validation that's shared by several services but not all of them?
  • How do you decide if a cross-cutting concern needs to be identical across every route, or configurable per route?
  • What's the risk of doing authentication at the gateway but authorization entirely in the service?

Q2 How do YARP, Azure API Management and a full cloud gateway or service mesh differ, and how would you choose among them?#

Short answer: YARP is a reverse-proxy library you host and code against, embedded in an ASP.NET Core app — maximum control and no separate managed service, at the cost of owning its deployment and scaling. Azure API Management is a managed platform with a policy engine, a developer portal and built-in API lifecycle features, trading some flexibility for far less operational ownership. A service mesh such as Istio or Linkerd operates at a different layer entirely, handling service-to-service traffic inside the cluster rather than the north-south edge traffic a gateway typically fronts.

FactorYARPAzure API ManagementService mesh
Operated byYour team, in-processMicrosoft, managedYour platform team, cluster-wide
CustomizationFull C# extensibilityPolicy-based (XML-like policies)Sidecar configuration
Best fit.NET-heavy teams needing custom logicAPI productization, external partnersEast-west traffic between many services
Config reloadLive, no restartPortal or ARM/Bicep deployCRDs, live

A senior-to-architect answer doesn't treat these as mutually exclusive: a common shape is API Management at the public edge for partner-facing APIs and developer onboarding, YARP or a mesh handling internal service-to-service routing, and the choice driven by who needs to operate it and how much custom logic the routing layer must run, not by which one is "better" in the abstract.

What interviewers look for: the operational-ownership trade-off stated clearly, and willingness to combine tools rather than pick one universal answer.

Common mistakes: treating a service mesh as a drop-in gateway replacement for public-facing traffic, when it's designed for cluster-internal traffic; choosing a managed gateway purely to avoid writing code, without weighing the loss of custom control.

Follow-up questions:

  • When would you run both API Management and YARP in the same system?
  • What does a service mesh give you that a gateway alone doesn't?
  • How would you migrate from a hand-rolled gateway to a managed one without a client-visible cutover?

Q3 Explain the Backend-for-Frontend pattern for a single-page app. Why keep access tokens in an HTTP-only cookie on the server instead of in the browser?#

Short answer: A BFF is a server-side component, one per frontend, that owns the OAuth/OIDC token exchange on the user's behalf: the SPA authenticates against the BFF using a session cookie, and the BFF holds the actual access and refresh tokens server-side, attaching them to outbound calls to downstream APIs. The browser never sees a bearer token it could leak through an XSS vulnerability, a browser extension, or a stray console.log.

Storing tokens in localStorage or a JavaScript-readable cookie makes them available to any script running on the page, so a single cross-site scripting flaw anywhere in a SPA's dependency tree hands an attacker a live, usable token. An HttpOnly, Secure, SameSite=Strict (or Lax) session cookie is invisible to JavaScript entirely; the SPA calls same-origin BFF endpoints, the BFF validates the session and forwards the request upstream with the real token attached as a header, and CSRF protection — required because cookies are sent automatically — is handled with the standard anti-forgery token pattern. Libraries such as Duende.BFF implement this pattern on top of ASP.NET Core's cookie authentication and OpenID Connect handlers rather than requiring it be hand-built.

What interviewers look for: XSS token theft named as the specific threat the pattern defends against, and CSRF named as the trade-off that comes with cookie-based sessions and needs its own mitigation.

Common mistakes: putting a JWT in localStorage "because it's simpler than managing a session," which trades a well-understood CSRF mitigation for an unbounded XSS blast radius; forgetting CSRF protection once cookies are in play.

Follow-up questions:

  • How does token refresh work without the SPA ever seeing a refresh token?
  • What changes about this pattern for a native mobile app instead of a SPA?
  • How would you handle logout across the BFF session and the identity provider's own session?

Q4 How do you decide between letting a client make several chatty backend calls versus building an aggregating gateway endpoint that combines them? What does aggregation cost you?#

Short answer: Aggregate when a client screen needs data from several services in one round trip and the client's network conditions (mobile, high latency) make several sequential calls expensive; stay chatty when the calls are independent, cacheable separately, or used by different parts of the UI at different times. Aggregation trades client-side round trips for a new piece of server-side code that has to know about multiple downstream services and their failure modes.

The real cost of an aggregating endpoint is that it becomes a distributed transaction in miniature: it must decide what to do when one of three downstream calls fails while the other two succeed, whether to return partial data or fail the whole request, and how to keep its own timeout tighter than the sum of its downstream calls issued in parallel. A BFF is often exactly where this aggregation belongs, since it already exists per frontend and already knows what that specific UI needs, rather than building one generic aggregation layer that tries to serve every client's shape of data at once.

C#
var productTask = catalog.GetProductAsync(id, ct);
var priceTask = pricing.GetPriceAsync(id, ct);
var stockTask = inventory.GetStockAsync(id, ct);
await Task.WhenAll(productTask, priceTask, stockTask);

What interviewers look for: partial-failure handling named as the real complexity aggregation introduces, not just "fewer round trips"; aggregation placed in a BFF rather than a generic shared layer.

Common mistakes: building one universal aggregation gateway that tries to serve every client, which recreates the "gateway monolith" problem; calling downstream services sequentially inside an aggregator instead of in parallel with a bounded timeout.

Follow-up questions:

  • What should an aggregating endpoint return if one of three downstream calls times out?
  • How would you cache an aggregated response when its underlying pieces change at different rates?
  • When does aggregation belong in the client instead of the gateway?

Q5 What cross-cutting concerns belong at the gateway layer, and how would you implement them in YARP without hardcoding policy logic per route?#

Short answer: Authentication, authorization, rate limiting, response caching and observability are the classic set, and YARP's design point for all of them is the same: define the policy once in code, then reference it by name per route in configuration, so adding a new route never means writing new policy logic. RouteConfig.AuthorizationPolicy and RouteConfig.RateLimiterPolicy both work this way, bound straight from the Routes section and reloadable without restarting the proxy.

JSON
{
  "ReverseProxy": {
    "Routes": {
      "orders": {
        "ClusterId": "orders-cluster",
        "AuthorizationPolicy": "authenticated",
        "RateLimiterPolicy": "standard",
        "Match": { "Path": "/orders/{**catch-all}" }
      }
    }
  }
}
C#
services.AddAuthorization(o => o.AddPolicy("authenticated", p => p.RequireAuthenticatedUser()));
services.AddRateLimiter(o => o.AddFixedWindowLimiter("standard", opt => opt.PermitLimit = 100));

The policies themselves are ordinary ASP.NET Core authorization and rate-limiting components, so the same skills and testing approach used for a regular API apply directly at the gateway. Observability is usually the odd one out: rather than a per-route policy, it's typically applied globally through middleware or OpenTelemetry instrumentation so every request gets consistent tracing regardless of route configuration, since inconsistent observability defeats its own purpose.

What interviewers look for: named policies referenced per route instead of duplicated logic, and observability correctly treated as a global default rather than an opt-in per-route policy.

Common mistakes: writing route-specific middleware for a concern that should be one named, reusable policy; applying rate limiting per route inconsistently so some routes are unprotected by omission.

Follow-up questions:

  • How would you apply a stricter rate limit only to unauthenticated traffic on the same route?
  • What would you centralize globally versus configure per route for logging?
  • How do you test a named authorization policy independently of the routes that reference it?

Q6 How do you keep an API gateway from becoming a "gateway monolith" that every team has to coordinate through before they can ship?#

Short answer: Give each team ownership of its own routes and policies instead of one shared configuration file that every change has to go through review on, and prefer a gateway that supports that split natively — YARP's configuration can be sourced from a custom IProxyConfigProvider, so routes can be assembled from several independently owned sources, such as one file or database record per service team, instead of a single hand-maintained block.

The organizational half of this matters as much as the technical half: a gateway becomes a monolith when it accumulates service-specific logic that only one team understands, when every route change requires a platform team's sign-off regardless of blast radius, or when a bug in one team's route configuration can take down another team's traffic because they share one deployable. Splitting ownership by route, giving teams a self-service way to register their own routes and policies within guardrails the platform team defines, and keeping the gateway itself free of anything service-specific are what keep it infrastructure rather than a second, harder-to-change application every team is entangled in.

What interviewers look for: both dimensions addressed — technical (config composed from several sources) and organizational (who can change what without a shared bottleneck) — not just a tooling answer.

Common mistakes: solving this purely with better tooling while every change still requires the same central team's approval; letting one team's route configuration or custom transform affect another team's traffic because they share a single gateway deployment with no isolation.

Follow-up questions:

  • What guardrails would you put around self-service route registration?
  • How would you detect that one team's gateway configuration is degrading another team's traffic?
  • When would you split into multiple gateway instances instead of one shared one?

Q7 Design a BFF that calls three downstream services for a SPA, one of which is an internal service requiring a different auth scheme, such as an API key or mutual TLS. How does token exchange work at the boundary?#

Short answer: The SPA only ever authenticates once, against the BFF, using its session cookie; the BFF is where credential translation happens, converting that one authenticated session into whatever each downstream service actually requires, rather than the frontend knowing about three different auth schemes.

C#
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
    .AddTransforms(ctx => ctx.AddRequestTransform(async transformCtx =>
    {
        if (transformCtx.Route.RouteId == "internal-pricing")
        {
            transformCtx.ProxyRequest.Headers.Add("X-Api-Key", pricingApiKeyProvider.Current);
        }
        else
        {
            var token = await tokenStore.GetAccessTokenAsync(transformCtx.HttpContext.User);
            transformCtx.ProxyRequest.Headers.Authorization = new("Bearer", token);
        }
    }));

For the two standard OAuth-protected services, the BFF attaches the user's access token as a bearer header, refreshing it transparently when it's near expiry. For the internal service with a different scheme, the BFF's request transform swaps in an API key or relies on mutual TLS configured at the transport layer between the BFF and that service — the SPA and its session cookie never touch that credential at all. This is also where a service-specific credential belongs, at the one edge that talks to it, rather than distributed to every client that might need to call it.

What interviewers look for: one session at the frontend translated into per-service credentials at the BFF, and the internal service's different scheme handled as a routing-layer concern the SPA never sees.

Common mistakes: giving the SPA a separate credential for the internal service directly, which defeats the purpose of centralizing token handling in the BFF; hardcoding a static API key in a way that makes rotation require a redeploy instead of a configuration change.

Follow-up questions:

  • How would you rotate the internal service's API key without downtime?
  • What changes if the internal service's mTLS certificate expires?
  • How would you audit which downstream calls used which credential for a given user request?

Q8 What happens to your gateway when a downstream service is slow or completely down, and how do you stop that from cascading to every client that passes through the gateway?#

Short answer: Without protection, a slow downstream service exhausts the gateway's own connection pool and threads as requests pile up waiting for it, which turns one unhealthy service into an outage for every other service routed through the same gateway. The fix is the standard resilience toolkit applied at the gateway boundary: per-destination timeouts, circuit breakers that stop sending traffic to a consistently failing destination, and health checks that pull a bad destination out of rotation before it accumulates a queue of doomed requests.

YARP's active and passive health checks remove an unhealthy destination from load balancing automatically, and a per-route or per-cluster timeout bounds how long a gateway will wait before failing fast instead of holding the connection open. The architectural point interviewers want stated clearly: this protection has to live at the gateway specifically, in addition to whatever resilience each service implements for its own downstream calls, because the gateway is the one component sitting in front of every client and is uniquely positioned to isolate one bad destination from affecting all the others. See the resilience and fault tolerance guide for the same patterns applied inside a service rather than at the edge.

What interviewers look for: the cascading-failure mechanism explained specifically (pool and thread exhaustion), and gateway-level isolation named as necessary in addition to, not instead of, per-service resilience.

Common mistakes: relying only on each backend service's own resilience and leaving the gateway itself without timeouts or circuit breaking; setting a gateway timeout longer than the client's own timeout, so the client gives up while the gateway is still waiting.

Follow-up questions:

  • How would you tune a circuit breaker's threshold differently for a critical versus a non-critical destination?
  • What should the gateway return to the client when a circuit is open?
  • How do active and passive health checks differ, and when would you use both?

Q9 How do you version APIs at the gateway so clients can upgrade independently of backend service releases?#

Short answer: Put the version in the contract the client sees — a URL segment, a header, or a media-type parameter — and let the gateway route each version to whichever backend deployment currently serves it, so a backend team can deploy a new version behind the gateway without forcing every client to move in lockstep. The gateway's routing rules, not the backend services' internal versioning, are what clients actually depend on.

This only works if backend services also commit to running two versions side by side during a migration window, since the gateway can route to different versions but can't make a backend service support clients it wasn't built for. A clean split of responsibility: the gateway owns client-facing contract versioning and traffic shifting between backend versions, while backend teams own how long they're willing to run N and N+1 simultaneously and when to sunset the old one, communicated through the gateway as a deprecation window rather than a hard cutover.

What interviewers look for: versioning treated as a gateway routing concern separate from backend deployment strategy, and an explicit plan for the overlap window rather than an assumed instant cutover.

Common mistakes: versioning only in backend service code with no gateway-level routing, which forces every client to switch the moment a new backend version ships; never defining a sunset date for an old version, so it runs forever "just in case."

Follow-up questions:

  • How would you migrate clients off a deprecated version without breaking anyone silently?
  • What's the trade-off between URL-based and header-based API versioning at the gateway?
  • How would you monitor which clients are still using a version you want to retire?

Q10 A product team wants the gateway to apply a discount rule before forwarding requests to the order service, since it would be "faster to change" there. How do you push back, and are there legitimate exceptions to keeping business logic out of the gateway?#

Short answer: Push back by pointing out that "faster to change" is true right up until two more teams add their own rules to the same shared, unowned layer, at which point every change to gateway logic risks every other team's traffic, and the order service's own tests and deployments no longer reflect what actually happens to a real order. The discount rule belongs in the order service, versioned and tested with the domain it affects.

The legitimate exceptions are narrow and specifically not business logic: request shaping that's uniform regardless of domain state, such as rejecting malformed requests before they cost a backend call, or applying a rule that's genuinely about traffic itself rather than business outcome, such as blocking a known-bad client pattern. The test is the same boundary question from gateway responsibilities in general — would this be identical no matter which backend handled the request? A discount rule depends on the specific order, the specific customer and the specific business rules the order service owns, so it fails that test outright, however convenient the shared deployment might look in the short term.

What interviewers look for: a concrete reason stated, not just "it's bad practice" — ownership, testability and blast radius named specifically — plus a fair acknowledgment of the narrow cases that really do belong at the edge.

Common mistakes: giving an absolute "never" with no legitimate exception named, which reads as dogma rather than judgment; agreeing to "just this once" without naming the concrete cost that follows.

Follow-up questions:

  • How would you migrate a discount rule that already lives in the gateway back into the order service safely?
  • What would make a genuinely edge-appropriate rule "business logic in disguise" instead?
  • How do you evaluate a request to add "just one more" rule to the gateway over time?

Quick-Fire Round#

QuestionAnswer
Where does fine-grained authorization belong?In the owning service, not the gateway.
What does a BFF keep out of browser JavaScript?Access and refresh tokens, held server-side.
Why does a cookie-based BFF session need CSRF protection?Because cookies are sent automatically, unlike a bearer header.
What's the main risk of a universal aggregation endpoint?It recreates the gateway-monolith problem for every client's shape of data.
How does YARP apply a policy to many routes without duplication?Named policies referenced per route, such as AuthorizationPolicy.
What stops one slow downstream service from taking down the gateway?Per-destination timeouts, circuit breakers and health checks.
Where should API version routing live?At the gateway, decoupled from backend deployment cadence.
Test for whether logic belongs at the gateway?Would it be identical no matter which backend handled the request?

How to Prepare#

  • Have a precise boundary test for gateway responsibilities versus service responsibilities, and defend it with a concrete example, not a memorized rule.
  • Be able to compare YARP, a managed API gateway and a service mesh by who operates them and what layer of traffic they're built for, and combine them instead of picking one universally.
  • Explain the BFF pattern's token-in-cookie design as a specific defense against XSS, and name the CSRF trade-off it introduces.
  • Practice designing an aggregating endpoint that handles partial downstream failure explicitly.
  • Have a real answer for avoiding a gateway monolith that covers both configuration composition and team ownership, not tooling alone.
  • Be ready to push back on business logic creeping into the gateway, with the boundary test as your reasoning, not just "that's an anti-pattern."